summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--AconfigFlags.bp19
-rw-r--r--Android.bp23
-rw-r--r--PREUPLOAD.cfg3
-rw-r--r--Ravenwood.bp1
-rw-r--r--apct-tests/perftests/core/src/android/input/MotionPredictorBenchmark.kt78
-rw-r--r--apct-tests/perftests/core/src/android/input/VelocityTrackerBenchmarkTest.kt61
-rw-r--r--apct-tests/perftests/core/src/android/os/TracePerfTest.java4
-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/OWNERS1
-rw-r--r--apct-tests/perftests/healthconnect/src/com/android/perftests/healthconnect/HealthConnectReadWritePerfTest.kt9
-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/app_idle.aconfig11
-rw-r--r--apex/jobscheduler/service/java/com/android/server/job/controllers/DeviceIdleJobsController.java34
-rw-r--r--apex/jobscheduler/service/java/com/android/server/usage/AppIdleHistory.java63
-rw-r--r--apex/jobscheduler/service/java/com/android/server/usage/AppStandbyController.java34
-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.java2
-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.txt21
-rw-r--r--core/api/module-lib-current.txt1
-rw-r--r--core/api/system-current.txt1
-rw-r--r--core/api/test-current.txt10
-rw-r--r--core/java/Android.bp9
-rw-r--r--core/java/android/animation/AnimationHandler.java6
-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.java72
-rw-r--r--core/java/android/app/ActivityManagerInternal.java8
-rw-r--r--core/java/android/app/ActivityOptions.java25
-rw-r--r--core/java/android/app/ActivityThread.java34
-rw-r--r--core/java/android/app/AppCompatTaskInfo.java23
-rw-r--r--core/java/android/app/AppOpsManager.java11
-rw-r--r--core/java/android/app/ContextImpl.java51
-rw-r--r--core/java/android/app/DreamManager.java7
-rw-r--r--core/java/android/app/IActivityManager.aidl4
-rw-r--r--core/java/android/app/LoadedApk.java14
-rw-r--r--core/java/android/app/Notification.java58
-rw-r--r--core/java/android/app/PropertyInvalidatedCache.java63
-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.java1
-rw-r--r--core/java/android/app/admin/PolicyUpdateReceiver.java31
-rw-r--r--core/java/android/app/admin/flags/flags.aconfig10
-rw-r--r--core/java/android/app/backup/BackupManagerMonitor.java3
-rw-r--r--core/java/android/app/contextualsearch/flags.aconfig12
-rw-r--r--core/java/android/app/jank/JankTracker.java75
-rw-r--r--core/java/android/app/jank/flags.aconfig10
-rw-r--r--core/java/android/app/notification.aconfig10
-rw-r--r--core/java/android/app/performance.aconfig7
-rw-r--r--core/java/android/app/servertransaction/ClientTransaction.java12
-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/BroadcastReceiver.java4
-rw-r--r--core/java/android/content/pm/ActivityInfo.java2
-rw-r--r--core/java/android/content/pm/RegisteredServicesCache.java33
-rw-r--r--core/java/android/content/pm/UserInfo.java5
-rw-r--r--core/java/android/content/pm/flags.aconfig8
-rw-r--r--core/java/android/content/pm/multiuser.aconfig9
-rw-r--r--core/java/android/content/pm/parsing/OWNERS1
-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/gesture/OWNERS1
-rw-r--r--core/java/android/hardware/camera2/CameraManager.java8
-rw-r--r--core/java/android/hardware/display/DisplayManagerInternal.java10
-rw-r--r--core/java/android/hardware/usb/IUsbManagerInternal.aidl26
-rw-r--r--core/java/android/inputmethodservice/InputMethodService.java12
-rw-r--r--core/java/android/metrics/OWNERS1
-rw-r--r--core/java/android/os/Build.java2
-rw-r--r--core/java/android/os/CombinedMessageQueue/MessageQueue.java41
-rw-r--r--core/java/android/os/ExternalVibration.java12
-rw-r--r--core/java/android/os/IBinder.java2
-rw-r--r--core/java/android/os/IpcDataCache.java15
-rw-r--r--core/java/android/os/Looper.java2
-rw-r--r--core/java/android/os/Message.java4
-rw-r--r--core/java/android/os/PerfettoTrackEventExtra.java50
-rw-r--r--core/java/android/os/PowerManagerInternal.java5
-rw-r--r--core/java/android/os/Process.java7
-rw-r--r--core/java/android/os/UserManager.java29
-rw-r--r--core/java/android/os/flags.aconfig16
-rw-r--r--core/java/android/os/health/SystemHealthManager.java10
-rw-r--r--core/java/android/os/vibrator/flags.aconfig11
-rw-r--r--core/java/android/permission/PermissionManager.java40
-rw-r--r--core/java/android/permission/flags.aconfig33
-rw-r--r--core/java/android/preference/PreferenceActivity.java4
-rw-r--r--core/java/android/preference/PreferenceScreen.java18
-rw-r--r--core/java/android/provider/Settings.java158
-rw-r--r--core/java/android/provider/Telephony.java67
-rw-r--r--core/java/android/security/advancedprotection/AdvancedProtectionManager.java10
-rw-r--r--core/java/android/security/flags.aconfig7
-rw-r--r--core/java/android/service/dreams/IDreamManager.aidl2
-rw-r--r--core/java/android/service/notification/Adjustment.java2
-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/service/wallpaper/WallpaperService.java7
-rw-r--r--core/java/android/text/OWNERS1
-rw-r--r--core/java/android/util/MapCollections.java37
-rw-r--r--core/java/android/view/ImeBackAnimationController.java25
-rw-r--r--core/java/android/view/InsetsController.java8
-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/OWNERS1
-rw-r--r--core/java/android/view/SurfaceView.java21
-rw-r--r--core/java/android/view/View.java11
-rw-r--r--core/java/android/view/ViewConfiguration.java231
-rw-r--r--core/java/android/view/ViewRootImpl.java113
-rw-r--r--core/java/android/view/ViewStructure.java13
-rw-r--r--core/java/android/view/WindowManager.java7
-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.java3
-rw-r--r--core/java/android/view/flags/view_flags.aconfig8
-rw-r--r--core/java/android/view/inspector/OWNERS1
-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/TEST_MAPPING9
-rw-r--r--core/java/android/webkit/WebChromeClient.java5
-rw-r--r--core/java/android/webkit/WebIconDatabase.java2
-rw-r--r--core/java/android/webkit/WebSettings.java7
-rw-r--r--core/java/android/webkit/WebView.java57
-rw-r--r--core/java/android/widget/TextView.java10
-rw-r--r--core/java/android/window/DesktopExperienceFlags.java20
-rw-r--r--core/java/android/window/DesktopModeFlags.java16
-rw-r--r--core/java/android/window/TransitionInfo.java4
-rw-r--r--core/java/android/window/WindowContainerTransaction.java32
-rw-r--r--core/java/android/window/flags/lse_desktop_experience.aconfig106
-rw-r--r--core/java/android/window/flags/windowing_frontend.aconfig41
-rw-r--r--core/java/android/window/flags/windowing_sdk.aconfig34
-rw-r--r--core/java/com/android/internal/accessibility/dialog/AccessibilityTargetHelper.java3
-rw-r--r--core/java/com/android/internal/config/appcloning/OWNERS1
-rw-r--r--core/java/com/android/internal/infra/OWNERS6
-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/os/DEBUG_STORE_OWNERS2
-rw-r--r--core/java/com/android/internal/os/DebugStore.java134
-rw-r--r--core/java/com/android/internal/os/OWNERS1
-rw-r--r--core/java/com/android/internal/policy/DecorView.java130
-rw-r--r--core/java/com/android/internal/protolog/PerfettoProtoLogImpl.java20
-rw-r--r--core/java/com/android/internal/util/function/pooled/OWNERS1
-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/NotificationExpandButton.java10
-rw-r--r--core/java/com/android/internal/widget/PeopleHelper.java2
-rw-r--r--core/java/com/android/internal/widget/remotecompose/OWNERS1
-rw-r--r--core/java/com/android/internal/widget/remotecompose/core/CoreDocument.java4
-rw-r--r--core/java/com/android/internal/widget/remotecompose/core/Operations.java3
-rw-r--r--core/java/com/android/internal/widget/remotecompose/core/PaintOperation.java4
-rw-r--r--core/java/com/android/internal/widget/remotecompose/core/Platform.java13
-rw-r--r--core/java/com/android/internal/widget/remotecompose/core/RemoteComposeBuffer.java129
-rw-r--r--core/java/com/android/internal/widget/remotecompose/core/RemoteComposeState.java11
-rw-r--r--core/java/com/android/internal/widget/remotecompose/core/RemoteContext.java8
-rw-r--r--core/java/com/android/internal/widget/remotecompose/core/operations/BitmapData.java36
-rw-r--r--core/java/com/android/internal/widget/remotecompose/core/operations/BitmapFontData.java2
-rw-r--r--core/java/com/android/internal/widget/remotecompose/core/operations/ClickArea.java2
-rw-r--r--core/java/com/android/internal/widget/remotecompose/core/operations/ClipPath.java2
-rw-r--r--core/java/com/android/internal/widget/remotecompose/core/operations/ClipRect.java2
-rw-r--r--core/java/com/android/internal/widget/remotecompose/core/operations/ColorConstant.java2
-rw-r--r--core/java/com/android/internal/widget/remotecompose/core/operations/ColorExpression.java2
-rw-r--r--core/java/com/android/internal/widget/remotecompose/core/operations/ComponentValue.java2
-rw-r--r--core/java/com/android/internal/widget/remotecompose/core/operations/DataListFloat.java3
-rw-r--r--core/java/com/android/internal/widget/remotecompose/core/operations/DataListIds.java3
-rw-r--r--core/java/com/android/internal/widget/remotecompose/core/operations/DrawArc.java2
-rw-r--r--core/java/com/android/internal/widget/remotecompose/core/operations/DrawBitmap.java13
-rw-r--r--core/java/com/android/internal/widget/remotecompose/core/operations/DrawBitmapFontText.java13
-rw-r--r--core/java/com/android/internal/widget/remotecompose/core/operations/DrawBitmapInt.java17
-rw-r--r--core/java/com/android/internal/widget/remotecompose/core/operations/DrawBitmapScaled.java43
-rw-r--r--core/java/com/android/internal/widget/remotecompose/core/operations/DrawCircle.java2
-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/DrawLine.java2
-rw-r--r--core/java/com/android/internal/widget/remotecompose/core/operations/DrawOval.java2
-rw-r--r--core/java/com/android/internal/widget/remotecompose/core/operations/DrawPath.java2
-rw-r--r--core/java/com/android/internal/widget/remotecompose/core/operations/DrawRect.java2
-rw-r--r--core/java/com/android/internal/widget/remotecompose/core/operations/DrawRoundRect.java2
-rw-r--r--core/java/com/android/internal/widget/remotecompose/core/operations/DrawSector.java2
-rw-r--r--core/java/com/android/internal/widget/remotecompose/core/operations/DrawText.java14
-rw-r--r--core/java/com/android/internal/widget/remotecompose/core/operations/DrawTextAnchored.java2
-rw-r--r--core/java/com/android/internal/widget/remotecompose/core/operations/DrawTextOnPath.java2
-rw-r--r--core/java/com/android/internal/widget/remotecompose/core/operations/DrawTweenPath.java12
-rw-r--r--core/java/com/android/internal/widget/remotecompose/core/operations/FloatConstant.java2
-rw-r--r--core/java/com/android/internal/widget/remotecompose/core/operations/FloatExpression.java13
-rw-r--r--core/java/com/android/internal/widget/remotecompose/core/operations/FloatFunctionCall.java11
-rw-r--r--core/java/com/android/internal/widget/remotecompose/core/operations/ImageAttribute.java23
-rw-r--r--core/java/com/android/internal/widget/remotecompose/core/operations/IntegerExpression.java2
-rw-r--r--core/java/com/android/internal/widget/remotecompose/core/operations/MatrixRestore.java2
-rw-r--r--core/java/com/android/internal/widget/remotecompose/core/operations/MatrixRotate.java2
-rw-r--r--core/java/com/android/internal/widget/remotecompose/core/operations/MatrixSave.java2
-rw-r--r--core/java/com/android/internal/widget/remotecompose/core/operations/MatrixScale.java2
-rw-r--r--core/java/com/android/internal/widget/remotecompose/core/operations/MatrixSkew.java2
-rw-r--r--core/java/com/android/internal/widget/remotecompose/core/operations/MatrixTranslate.java2
-rw-r--r--core/java/com/android/internal/widget/remotecompose/core/operations/NamedVariable.java9
-rw-r--r--core/java/com/android/internal/widget/remotecompose/core/operations/PaintData.java2
-rw-r--r--core/java/com/android/internal/widget/remotecompose/core/operations/ParticlesLoop.java6
-rw-r--r--core/java/com/android/internal/widget/remotecompose/core/operations/PathAppend.java5
-rw-r--r--core/java/com/android/internal/widget/remotecompose/core/operations/PathCreate.java5
-rw-r--r--core/java/com/android/internal/widget/remotecompose/core/operations/PathData.java5
-rw-r--r--core/java/com/android/internal/widget/remotecompose/core/operations/PathTween.java2
-rw-r--r--core/java/com/android/internal/widget/remotecompose/core/operations/RootContentDescription.java2
-rw-r--r--core/java/com/android/internal/widget/remotecompose/core/operations/ShaderData.java2
-rw-r--r--core/java/com/android/internal/widget/remotecompose/core/operations/TextAttribute.java2
-rw-r--r--core/java/com/android/internal/widget/remotecompose/core/operations/TextData.java2
-rw-r--r--core/java/com/android/internal/widget/remotecompose/core/operations/TextFromFloat.java2
-rw-r--r--core/java/com/android/internal/widget/remotecompose/core/operations/TextLookup.java2
-rw-r--r--core/java/com/android/internal/widget/remotecompose/core/operations/TextLookupInt.java2
-rw-r--r--core/java/com/android/internal/widget/remotecompose/core/operations/TextMeasure.java30
-rw-r--r--core/java/com/android/internal/widget/remotecompose/core/operations/TextMerge.java2
-rw-r--r--core/java/com/android/internal/widget/remotecompose/core/operations/TimeAttribute.java46
-rw-r--r--core/java/com/android/internal/widget/remotecompose/core/operations/TouchExpression.java4
-rw-r--r--core/java/com/android/internal/widget/remotecompose/core/operations/layout/AnimatableValue.java2
-rw-r--r--core/java/com/android/internal/widget/remotecompose/core/operations/layout/CanvasOperations.java2
-rw-r--r--core/java/com/android/internal/widget/remotecompose/core/operations/layout/ClickModifierOperation.java2
-rw-r--r--core/java/com/android/internal/widget/remotecompose/core/operations/layout/Component.java188
-rw-r--r--core/java/com/android/internal/widget/remotecompose/core/operations/layout/ImpulseOperation.java9
-rw-r--r--core/java/com/android/internal/widget/remotecompose/core/operations/layout/ImpulseProcess.java2
-rw-r--r--core/java/com/android/internal/widget/remotecompose/core/operations/layout/LoopOperation.java4
-rw-r--r--core/java/com/android/internal/widget/remotecompose/core/operations/layout/RootLayoutComponent.java6
-rw-r--r--core/java/com/android/internal/widget/remotecompose/core/operations/layout/TouchCancelModifierOperation.java2
-rw-r--r--core/java/com/android/internal/widget/remotecompose/core/operations/layout/TouchDownModifierOperation.java2
-rw-r--r--core/java/com/android/internal/widget/remotecompose/core/operations/layout/TouchUpModifierOperation.java2
-rw-r--r--core/java/com/android/internal/widget/remotecompose/core/operations/layout/animation/AnimateMeasure.java19
-rw-r--r--core/java/com/android/internal/widget/remotecompose/core/operations/layout/animation/AnimationSpec.java2
-rw-r--r--core/java/com/android/internal/widget/remotecompose/core/operations/layout/managers/BoxLayout.java6
-rw-r--r--core/java/com/android/internal/widget/remotecompose/core/operations/layout/managers/CanvasLayout.java2
-rw-r--r--core/java/com/android/internal/widget/remotecompose/core/operations/layout/managers/CollapsibleColumnLayout.java70
-rw-r--r--core/java/com/android/internal/widget/remotecompose/core/operations/layout/managers/CollapsibleRowLayout.java43
-rw-r--r--core/java/com/android/internal/widget/remotecompose/core/operations/layout/managers/ColumnLayout.java29
-rw-r--r--core/java/com/android/internal/widget/remotecompose/core/operations/layout/managers/FitBoxLayout.java371
-rw-r--r--core/java/com/android/internal/widget/remotecompose/core/operations/layout/managers/LayoutManager.java17
-rw-r--r--core/java/com/android/internal/widget/remotecompose/core/operations/layout/managers/RowLayout.java39
-rw-r--r--core/java/com/android/internal/widget/remotecompose/core/operations/layout/managers/StateLayout.java4
-rw-r--r--core/java/com/android/internal/widget/remotecompose/core/operations/layout/managers/TextLayout.java10
-rw-r--r--core/java/com/android/internal/widget/remotecompose/core/operations/layout/measure/ComponentMeasure.java47
-rw-r--r--core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/BackgroundModifierOperation.java2
-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/ClipRectModifierOperation.java2
-rw-r--r--core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/ComponentModifiers.java2
-rw-r--r--core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/ComponentVisibilityOperation.java12
-rw-r--r--core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/DrawContentOperation.java2
-rw-r--r--core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/GraphicsLayerModifierOperation.java2
-rw-r--r--core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/HeightInModifierOperation.java2
-rw-r--r--core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/HeightModifierOperation.java2
-rw-r--r--core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/HostActionOperation.java2
-rw-r--r--core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/HostNamedActionOperation.java2
-rw-r--r--core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/MarqueeModifierOperation.java6
-rw-r--r--core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/OffsetModifierOperation.java2
-rw-r--r--core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/PaddingModifierOperation.java2
-rw-r--r--core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/RippleModifierOperation.java2
-rw-r--r--core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/RoundedClipRectModifierOperation.java2
-rw-r--r--core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/ScrollModifierOperation.java2
-rw-r--r--core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/ValueFloatChangeActionOperation.java2
-rw-r--r--core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/ValueFloatExpressionChangeActionOperation.java2
-rw-r--r--core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/ValueIntegerChangeActionOperation.java2
-rw-r--r--core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/ValueIntegerExpressionChangeActionOperation.java2
-rw-r--r--core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/ValueStringChangeActionOperation.java2
-rw-r--r--core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/WidthInModifierOperation.java2
-rw-r--r--core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/WidthModifierOperation.java2
-rw-r--r--core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/ZIndexModifierOperation.java2
-rw-r--r--core/java/com/android/internal/widget/remotecompose/core/operations/paint/PaintBundle.java4
-rw-r--r--core/java/com/android/internal/widget/remotecompose/core/operations/utilities/easing/FloatAnimation.java41
-rw-r--r--core/java/com/android/internal/widget/remotecompose/core/semantics/CoreSemantics.java2
-rw-r--r--core/java/com/android/internal/widget/remotecompose/core/serialize/MapSerializer.java12
-rw-r--r--core/java/com/android/internal/widget/remotecompose/core/types/BooleanConstant.java2
-rw-r--r--core/java/com/android/internal/widget/remotecompose/core/types/IntegerConstant.java2
-rw-r--r--core/java/com/android/internal/widget/remotecompose/core/types/LongConstant.java17
-rw-r--r--core/java/com/android/internal/widget/remotecompose/player/RemoteComposePlayer.java10
-rw-r--r--core/java/com/android/internal/widget/remotecompose/player/platform/AndroidPaintContext.java5
-rw-r--r--core/java/com/android/internal/widget/remotecompose/player/platform/AndroidPlatformServices.java8
-rw-r--r--core/java/com/android/internal/widget/remotecompose/player/platform/AndroidRemoteContext.java39
-rw-r--r--core/java/com/android/internal/widget/remotecompose/player/platform/RemoteComposeCanvas.java10
-rw-r--r--core/jni/Android.bp4
-rw-r--r--core/jni/android_database_SQLiteConnection.cpp13
-rw-r--r--core/jni/android_media_ImageWriter.cpp2
-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.cpp16
-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/net/OWNERS1
-rw-r--r--core/proto/android/providers/settings/secure.proto11
-rw-r--r--core/proto/android/server/windowmanagerservice.proto28
-rw-r--r--core/res/AndroidManifest.xml6
-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/drawable/ic_accessibility_autoclick.xml8
-rw-r--r--core/res/res/drawable/ic_accessibility_autoclick_foreground.xml17
-rw-r--r--core/res/res/drawable/ic_notification_summarization.xml17
-rw-r--r--core/res/res/layout/accessibility_autoclick_scroll_panel.xml92
-rw-r--r--core/res/res/layout/accessibility_autoclick_type_panel.xml4
-rw-r--r--core/res/res/layout/notification_2025_expand_button.xml1
-rw-r--r--core/res/res/layout/notification_2025_right_icon.xml31
-rw-r--r--core/res/res/layout/notification_2025_template_collapsed_base.xml24
-rw-r--r--core/res/res/layout/notification_2025_template_collapsed_call.xml15
-rw-r--r--core/res/res/layout/notification_2025_template_collapsed_conversation.xml11
-rw-r--r--core/res/res/layout/notification_2025_template_collapsed_media.xml24
-rw-r--r--core/res/res/layout/notification_2025_template_collapsed_messaging.xml11
-rw-r--r--core/res/res/layout/notification_2025_template_expanded_base.xml2
-rw-r--r--core/res/res/layout/notification_2025_template_expanded_big_picture.xml2
-rw-r--r--core/res/res/layout/notification_2025_template_expanded_big_text.xml2
-rw-r--r--core/res/res/layout/notification_2025_template_expanded_call.xml2
-rw-r--r--core/res/res/layout/notification_2025_template_expanded_conversation.xml2
-rw-r--r--core/res/res/layout/notification_2025_template_expanded_inbox.xml2
-rw-r--r--core/res/res/layout/notification_2025_template_expanded_media.xml2
-rw-r--r--core/res/res/layout/notification_2025_template_expanded_messaging.xml2
-rw-r--r--core/res/res/layout/notification_2025_template_expanded_progress.xml2
-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.xml7
-rw-r--r--core/res/res/values-am/strings.xml7
-rw-r--r--core/res/res/values-ar/strings.xml7
-rw-r--r--core/res/res/values-as/strings.xml7
-rw-r--r--core/res/res/values-az/strings.xml7
-rw-r--r--core/res/res/values-b+sr+Latn/strings.xml7
-rw-r--r--core/res/res/values-be/strings.xml9
-rw-r--r--core/res/res/values-bg/strings.xml7
-rw-r--r--core/res/res/values-bn/strings.xml9
-rw-r--r--core/res/res/values-bs/strings.xml7
-rw-r--r--core/res/res/values-ca/strings.xml7
-rw-r--r--core/res/res/values-cs/strings.xml7
-rw-r--r--core/res/res/values-da/strings.xml7
-rw-r--r--core/res/res/values-de/strings.xml7
-rw-r--r--core/res/res/values-el/strings.xml7
-rw-r--r--core/res/res/values-en-rAU/strings.xml7
-rw-r--r--core/res/res/values-en-rCA/strings.xml3
-rw-r--r--core/res/res/values-en-rGB/strings.xml7
-rw-r--r--core/res/res/values-en-rIN/strings.xml7
-rw-r--r--core/res/res/values-es-rUS/strings.xml7
-rw-r--r--core/res/res/values-es/strings.xml7
-rw-r--r--core/res/res/values-et/strings.xml15
-rw-r--r--core/res/res/values-eu/strings.xml7
-rw-r--r--core/res/res/values-fa/strings.xml7
-rw-r--r--core/res/res/values-fi/strings.xml7
-rw-r--r--core/res/res/values-fr-rCA/strings.xml7
-rw-r--r--core/res/res/values-fr/strings.xml7
-rw-r--r--core/res/res/values-gl/strings.xml7
-rw-r--r--core/res/res/values-gu/strings.xml4
-rw-r--r--core/res/res/values-hi/strings.xml7
-rw-r--r--core/res/res/values-hr/strings.xml23
-rw-r--r--core/res/res/values-hu/strings.xml7
-rw-r--r--core/res/res/values-hy/strings.xml7
-rw-r--r--core/res/res/values-in/strings.xml7
-rw-r--r--core/res/res/values-is/strings.xml9
-rw-r--r--core/res/res/values-it/strings.xml11
-rw-r--r--core/res/res/values-iw/strings.xml4
-rw-r--r--core/res/res/values-ja/strings.xml7
-rw-r--r--core/res/res/values-ka/strings.xml7
-rw-r--r--core/res/res/values-kk/strings.xml7
-rw-r--r--core/res/res/values-km/strings.xml7
-rw-r--r--core/res/res/values-kn/strings.xml7
-rw-r--r--core/res/res/values-ko/strings.xml7
-rw-r--r--core/res/res/values-ky/strings.xml11
-rw-r--r--core/res/res/values-lo/strings.xml7
-rw-r--r--core/res/res/values-lt/strings.xml7
-rw-r--r--core/res/res/values-lv/strings.xml7
-rw-r--r--core/res/res/values-mk/strings.xml9
-rw-r--r--core/res/res/values-ml/strings.xml7
-rw-r--r--core/res/res/values-mn/strings.xml9
-rw-r--r--core/res/res/values-mr/strings.xml47
-rw-r--r--core/res/res/values-ms/strings.xml7
-rw-r--r--core/res/res/values-my/strings.xml7
-rw-r--r--core/res/res/values-nb/strings.xml7
-rw-r--r--core/res/res/values-ne/strings.xml7
-rw-r--r--core/res/res/values-nl/strings.xml7
-rw-r--r--core/res/res/values-or/strings.xml7
-rw-r--r--core/res/res/values-pa/strings.xml7
-rw-r--r--core/res/res/values-pl/strings.xml9
-rw-r--r--core/res/res/values-pt-rBR/strings.xml7
-rw-r--r--core/res/res/values-pt-rPT/strings.xml9
-rw-r--r--core/res/res/values-pt/strings.xml7
-rw-r--r--core/res/res/values-ro/strings.xml9
-rw-r--r--core/res/res/values-ru/strings.xml7
-rw-r--r--core/res/res/values-si/strings.xml9
-rw-r--r--core/res/res/values-sk/strings.xml7
-rw-r--r--core/res/res/values-sl/strings.xml7
-rw-r--r--core/res/res/values-sq/strings.xml15
-rw-r--r--core/res/res/values-sr/strings.xml7
-rw-r--r--core/res/res/values-sv/strings.xml9
-rw-r--r--core/res/res/values-sw/strings.xml7
-rw-r--r--core/res/res/values-ta/strings.xml7
-rw-r--r--core/res/res/values-te/strings.xml7
-rw-r--r--core/res/res/values-th/strings.xml9
-rw-r--r--core/res/res/values-tl/strings.xml7
-rw-r--r--core/res/res/values-tr/strings.xml7
-rw-r--r--core/res/res/values-uk/strings.xml7
-rw-r--r--core/res/res/values-ur/strings.xml7
-rw-r--r--core/res/res/values-uz/strings.xml7
-rw-r--r--core/res/res/values-vi/strings.xml7
-rw-r--r--core/res/res/values-zh-rCN/strings.xml7
-rw-r--r--core/res/res/values-zh-rHK/strings.xml7
-rw-r--r--core/res/res/values-zh-rTW/strings.xml7
-rw-r--r--core/res/res/values-zu/strings.xml7
-rw-r--r--core/res/res/values/attrs_manifest.xml17
-rw-r--r--core/res/res/values/colors.xml1
-rw-r--r--core/res/res/values/config.xml55
-rw-r--r--core/res/res/values/config_telephony.xml10
-rw-r--r--core/res/res/values/dimens.xml28
-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.xml48
-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/app/PropertyInvalidatedCacheTests.java25
-rw-r--r--core/tests/coretests/src/android/content/pm/OWNERS1
-rw-r--r--core/tests/coretests/src/android/content/pm/RegisteredServicesCacheTest.java6
-rw-r--r--core/tests/coretests/src/android/os/IpcDataCacheTest.java26
-rw-r--r--core/tests/coretests/src/android/os/PerfettoTraceTest.java2
-rw-r--r--core/tests/coretests/src/android/util/ArrayMapTest.java51
-rw-r--r--core/tests/coretests/src/android/view/ViewRootImplTest.java115
-rw-r--r--core/tests/coretests/src/android/window/WindowOnBackInvokedDispatcherTest.java12
-rw-r--r--core/tests/coretests/src/com/android/internal/app/ChooserActivityWorkProfileTest.java3
-rw-r--r--core/tests/coretests/src/com/android/internal/os/DebugStoreTest.java354
-rw-r--r--core/tests/coretests/src/com/android/internal/os/OWNERS1
-rw-r--r--core/tests/featureflagtests/OWNERS3
-rw-r--r--core/tests/vibrator/src/android/os/ExternalVibrationTest.java18
-rw-r--r--data/etc/OWNERS1
-rw-r--r--data/etc/privapp-permissions-platform.xml2
-rw-r--r--graphics/java/android/graphics/Paint.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/multitasking.aconfig10
-rw-r--r--libs/WindowManager/Shell/multivalentScreenshotTests/src/com/android/wm/shell/shared/bubbles/DragZoneFactoryScreenshotTest.kt1
-rw-r--r--libs/WindowManager/Shell/res/layout/desktop_mode_window_decor_handle_menu.xml14
-rw-r--r--libs/WindowManager/Shell/res/layout/desktop_mode_window_decor_handle_menu_action_button.xml1
-rw-r--r--libs/WindowManager/Shell/res/values-af/strings.xml30
-rw-r--r--libs/WindowManager/Shell/res/values-am/strings.xml30
-rw-r--r--libs/WindowManager/Shell/res/values-ar/strings.xml30
-rw-r--r--libs/WindowManager/Shell/res/values-as/strings.xml30
-rw-r--r--libs/WindowManager/Shell/res/values-az/strings.xml30
-rw-r--r--libs/WindowManager/Shell/res/values-b+sr+Latn/strings.xml30
-rw-r--r--libs/WindowManager/Shell/res/values-be/strings.xml30
-rw-r--r--libs/WindowManager/Shell/res/values-bg/strings.xml30
-rw-r--r--libs/WindowManager/Shell/res/values-bn/strings.xml30
-rw-r--r--libs/WindowManager/Shell/res/values-bs/strings.xml30
-rw-r--r--libs/WindowManager/Shell/res/values-ca/strings.xml30
-rw-r--r--libs/WindowManager/Shell/res/values-cs/strings.xml30
-rw-r--r--libs/WindowManager/Shell/res/values-da/strings.xml30
-rw-r--r--libs/WindowManager/Shell/res/values-de/strings.xml30
-rw-r--r--libs/WindowManager/Shell/res/values-el/strings.xml30
-rw-r--r--libs/WindowManager/Shell/res/values-en-rAU/strings.xml30
-rw-r--r--libs/WindowManager/Shell/res/values-en-rGB/strings.xml30
-rw-r--r--libs/WindowManager/Shell/res/values-en-rIN/strings.xml30
-rw-r--r--libs/WindowManager/Shell/res/values-es-rUS/strings.xml32
-rw-r--r--libs/WindowManager/Shell/res/values-es/strings.xml30
-rw-r--r--libs/WindowManager/Shell/res/values-et/strings.xml30
-rw-r--r--libs/WindowManager/Shell/res/values-eu/strings.xml30
-rw-r--r--libs/WindowManager/Shell/res/values-fa/strings.xml30
-rw-r--r--libs/WindowManager/Shell/res/values-fi/strings.xml30
-rw-r--r--libs/WindowManager/Shell/res/values-fr-rCA/strings.xml30
-rw-r--r--libs/WindowManager/Shell/res/values-fr/strings.xml30
-rw-r--r--libs/WindowManager/Shell/res/values-gl/strings.xml30
-rw-r--r--libs/WindowManager/Shell/res/values-hi/strings.xml30
-rw-r--r--libs/WindowManager/Shell/res/values-hr/strings.xml30
-rw-r--r--libs/WindowManager/Shell/res/values-hu/strings.xml30
-rw-r--r--libs/WindowManager/Shell/res/values-hy/strings.xml30
-rw-r--r--libs/WindowManager/Shell/res/values-in/strings.xml30
-rw-r--r--libs/WindowManager/Shell/res/values-is/strings.xml30
-rw-r--r--libs/WindowManager/Shell/res/values-it/strings.xml30
-rw-r--r--libs/WindowManager/Shell/res/values-ja/strings.xml30
-rw-r--r--libs/WindowManager/Shell/res/values-ka/strings.xml30
-rw-r--r--libs/WindowManager/Shell/res/values-kk/strings.xml30
-rw-r--r--libs/WindowManager/Shell/res/values-km/strings.xml30
-rw-r--r--libs/WindowManager/Shell/res/values-kn/strings.xml30
-rw-r--r--libs/WindowManager/Shell/res/values-ko/strings.xml30
-rw-r--r--libs/WindowManager/Shell/res/values-ky/strings.xml30
-rw-r--r--libs/WindowManager/Shell/res/values-lo/strings.xml30
-rw-r--r--libs/WindowManager/Shell/res/values-lt/strings.xml30
-rw-r--r--libs/WindowManager/Shell/res/values-lv/strings.xml30
-rw-r--r--libs/WindowManager/Shell/res/values-mk/strings.xml30
-rw-r--r--libs/WindowManager/Shell/res/values-ml/strings.xml30
-rw-r--r--libs/WindowManager/Shell/res/values-mn/strings.xml30
-rw-r--r--libs/WindowManager/Shell/res/values-mr/strings.xml30
-rw-r--r--libs/WindowManager/Shell/res/values-ms/strings.xml30
-rw-r--r--libs/WindowManager/Shell/res/values-my/strings.xml30
-rw-r--r--libs/WindowManager/Shell/res/values-nb/strings.xml30
-rw-r--r--libs/WindowManager/Shell/res/values-ne/strings.xml30
-rw-r--r--libs/WindowManager/Shell/res/values-nl/strings.xml30
-rw-r--r--libs/WindowManager/Shell/res/values-or/strings.xml30
-rw-r--r--libs/WindowManager/Shell/res/values-pa/strings.xml30
-rw-r--r--libs/WindowManager/Shell/res/values-pl/strings.xml30
-rw-r--r--libs/WindowManager/Shell/res/values-pt-rBR/strings.xml30
-rw-r--r--libs/WindowManager/Shell/res/values-pt-rPT/strings.xml30
-rw-r--r--libs/WindowManager/Shell/res/values-pt/strings.xml30
-rw-r--r--libs/WindowManager/Shell/res/values-ro/strings.xml30
-rw-r--r--libs/WindowManager/Shell/res/values-ru/strings.xml30
-rw-r--r--libs/WindowManager/Shell/res/values-si/strings.xml30
-rw-r--r--libs/WindowManager/Shell/res/values-sk/strings.xml30
-rw-r--r--libs/WindowManager/Shell/res/values-sl/strings.xml30
-rw-r--r--libs/WindowManager/Shell/res/values-sq/strings.xml34
-rw-r--r--libs/WindowManager/Shell/res/values-sr/strings.xml30
-rw-r--r--libs/WindowManager/Shell/res/values-sv/strings.xml30
-rw-r--r--libs/WindowManager/Shell/res/values-sw/strings.xml30
-rw-r--r--libs/WindowManager/Shell/res/values-ta/strings.xml30
-rw-r--r--libs/WindowManager/Shell/res/values-te/strings.xml30
-rw-r--r--libs/WindowManager/Shell/res/values-th/strings.xml30
-rw-r--r--libs/WindowManager/Shell/res/values-tl/strings.xml30
-rw-r--r--libs/WindowManager/Shell/res/values-tr/strings.xml30
-rw-r--r--libs/WindowManager/Shell/res/values-uk/strings.xml30
-rw-r--r--libs/WindowManager/Shell/res/values-ur/strings.xml30
-rw-r--r--libs/WindowManager/Shell/res/values-uz/strings.xml30
-rw-r--r--libs/WindowManager/Shell/res/values-vi/strings.xml30
-rw-r--r--libs/WindowManager/Shell/res/values-zh-rCN/strings.xml30
-rw-r--r--libs/WindowManager/Shell/res/values-zh-rHK/strings.xml30
-rw-r--r--libs/WindowManager/Shell/res/values-zh-rTW/strings.xml30
-rw-r--r--libs/WindowManager/Shell/res/values-zu/strings.xml30
-rw-r--r--libs/WindowManager/Shell/res/values/dimen.xml6
-rw-r--r--libs/WindowManager/Shell/res/values/strings.xml8
-rw-r--r--libs/WindowManager/Shell/shared/res/values/dimen.xml2
-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.kt61
-rw-r--r--libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/bubbles/DropTargetView.kt71
-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.java24
-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.java114
-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/BubblePositioner.java5
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleTransitions.java82
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/IBubbles.aidl8
-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.java93
-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/HomeIntentProvider.kt65
-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/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.java19
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java107
-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/DesktopMinimizationTransitionHandler.kt11
-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/DesktopModeMoveToDisplayTransitionHandler.kt101
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeShellCommandHandler.kt9
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeUtils.kt61
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeVisualIndicator.java31
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopRepository.kt87
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTaskChangeListener.kt38
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt805
-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.kt21
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DragToDesktopTransitionHandler.kt195
-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/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/docs/dagger.md66
-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.java10
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/freeform/OWNERS1
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/keyguard/KeyguardTransitionHandler.java4
-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/pip/tv/OWNERS1
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/pip2/animation/PipAlphaAnimator.java3
-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.java15
-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/PipResizeGestureHandler.java47
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipScheduler.java45
-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.java45
-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.java61
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/tv/OWNERS1
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultTransitionHandler.java6
-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.java61
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java24
-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/HandleMenu.kt85
-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/MoveToDesktopAnimator.kt4
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecoration.java6
-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.kt6
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/viewholder/AppHandleViewHolder.kt2
-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.kt86
-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/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/bubbles/BubbleTransitionsTest.java38
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/animation/ExpandedAnimationControllerTest.java1
-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.java89
-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.kt72
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopDisplayModeControllerTest.kt71
-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/DesktopMinimizationTransitionHandlerTest.kt19
-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/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/DesktopRepositoryTest.kt53
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTaskChangeListenerTest.kt71
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksControllerTest.kt1338
-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.kt66
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTestHelpers.kt6
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DragToDesktopTransitionHandlerTest.kt190
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/VisualIndicatorViewContainerTest.kt34
-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/pip/tv/OWNERS3
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip2/animation/PipAlphaAnimatorTest.java64
-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.java35
-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/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.kt41
-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.java2
-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/DesktopModeWindowDecorViewModelTests.kt5
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModelTestsBase.kt7
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorationTests.java41
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/WindowDecorationTests.java46
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/common/WindowDecorTaskResourceLoaderTest.kt17
-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.kt65
-rw-r--r--libs/androidfw/include/androidfw/ResourceTypes.h10
-rw-r--r--libs/hostgraphics/HostBufferQueue.cpp1
-rw-r--r--libs/hwui/CanvasTransform.cpp44
-rw-r--r--libs/hwui/OWNERS2
-rw-r--r--libs/hwui/RenderNode.cpp33
-rw-r--r--libs/hwui/apex/LayoutlibLoader.cpp2
-rw-r--r--libs/hwui/hwui/Bitmap.cpp13
-rw-r--r--libs/hwui/hwui/Bitmap.h1
-rw-r--r--libs/hwui/jni/GIFMovie.cpp10
-rw-r--r--libs/hwui/jni/Graphics.cpp13
-rw-r--r--location/java/android/location/LocationRequest.java9
-rw-r--r--location/java/android/location/flags/location.aconfig18
-rw-r--r--location/java/com/android/internal/location/GpsNetInitiatedHandler.java3
-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.java154
-rw-r--r--media/java/android/media/MediaRoute2ProviderService.java17
-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/Utils.java34
-rw-r--r--media/java/android/media/flags/media_better_together.aconfig8
-rw-r--r--media/java/android/media/quality/Android.bp39
-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)0
-rw-r--r--media/java/android/media/quality/aidl/android/media/quality/AmbientBacklightEvent.aidl (renamed from media/java/android/media/quality/AmbientBacklightEvent.aidl)0
-rw-r--r--media/java/android/media/quality/aidl/android/media/quality/AmbientBacklightMetadata.aidl (renamed from media/java/android/media/quality/AmbientBacklightMetadata.aidl)0
-rw-r--r--media/java/android/media/quality/aidl/android/media/quality/AmbientBacklightSettings.aidl (renamed from media/java/android/media/quality/AmbientBacklightSettings.aidl)0
-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)0
-rw-r--r--media/java/android/media/quality/aidl/android/media/quality/PictureProfile.aidl (renamed from media/java/android/media/quality/PictureProfile.aidl)0
-rw-r--r--media/java/android/media/quality/aidl/android/media/quality/PictureProfileHandle.aidl (renamed from media/java/android/media/quality/PictureProfileHandle.aidl)0
-rw-r--r--media/java/android/media/quality/aidl/android/media/quality/SoundProfile.aidl (renamed from media/java/android/media/quality/SoundProfile.aidl)0
-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/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--native/android/performance_hint.cpp2
-rw-r--r--native/android/surface_control.cpp2
-rw-r--r--native/android/thermal.cpp14
-rw-r--r--native/webview/TEST_MAPPING9
-rw-r--r--packages/CompanionDeviceManager/res/layout/activity_confirmation.xml1
-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.xml6
-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.java5
-rw-r--r--packages/CompanionDeviceManager/src/com/android/companiondevicemanager/DeviceListAdapter.java2
-rw-r--r--packages/DynamicSystemInstallationService/res/values-mr/strings.xml12
-rw-r--r--packages/EasterEgg/src/com/android/egg/landroid/Autopilot.kt2
-rw-r--r--packages/EasterEgg/src/com/android/egg/landroid/MainActivity.kt4
-rw-r--r--packages/EasterEgg/src/com/android/egg/landroid/UniverseProgressNotifier.kt2
-rw-r--r--packages/SettingsLib/CollapsingToolbarBaseActivity/res/layout-v36/settingslib_expressive_collapsing_toolbar_content_layout.xml1
-rw-r--r--packages/SettingsLib/IllustrationPreference/src/com/android/settingslib/widget/IllustrationPreference.java12
-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/MainSwitchPreference.java21
-rw-r--r--packages/SettingsLib/OWNERS1
-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/src/com/android/settingslib/widget/NormalPaddingMixin.kt25
-rw-r--r--packages/SettingsLib/SettingsTheme/src/com/android/settingslib/widget/SettingsPreferenceGroupAdapter.kt22
-rw-r--r--packages/SettingsLib/SliderPreference/src/com/android/settingslib/widget/SliderPreference.java26
-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-fr/strings.xml2
-rw-r--r--packages/SettingsLib/res/values-mr/strings.xml2
-rw-r--r--packages/SettingsLib/res/values-pt-rPT/strings.xml2
-rw-r--r--packages/SettingsLib/res/values-vi/strings.xml2
-rw-r--r--packages/SettingsLib/res/values/strings.xml2
-rw-r--r--packages/SettingsLib/src/com/android/settingslib/RestrictedPreferenceHelper.java13
-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.java88
-rw-r--r--packages/SettingsLib/src/com/android/settingslib/wifi/WifiUtils.kt1
-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.java76
-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/widget/MainSwitchPreferenceTest.java36
-rw-r--r--packages/SettingsProvider/res/values/defaults.xml3
-rw-r--r--packages/SettingsProvider/src/android/provider/settings/backup/SecureSettings.java8
-rw-r--r--packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java13
-rw-r--r--packages/SettingsProvider/src/com/android/providers/settings/DeviceConfigService.java1
-rw-r--r--packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java16
-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/OWNERS2
-rw-r--r--packages/Shell/res/values-et/strings.xml2
-rw-r--r--packages/SystemUI/Android.bp1
-rw-r--r--packages/SystemUI/BUILD_OWNERS22
-rw-r--r--packages/SystemUI/OWNERS3
-rw-r--r--packages/SystemUI/aconfig/predictive_back.aconfig10
-rw-r--r--packages/SystemUI/aconfig/systemui.aconfig45
-rw-r--r--packages/SystemUI/animation/src/com/android/systemui/animation/ActivityTransitionAnimator.kt25
-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/TextAnimator.kt1
-rw-r--r--packages/SystemUI/animation/src/com/android/systemui/animation/TransitionAnimator.kt7
-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/RunTestShouldUseKosmosDetector.kt6
-rw-r--r--packages/SystemUI/checks/tests/com/android/internal/systemui/lint/DoNotDirectlyConstructKosmosTest.kt104
-rw-r--r--packages/SystemUI/compose/core/src/com/android/compose/PlatformButtons.kt6
-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/ui/graphics/DrawInContainer.kt32
-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/CommunalContent.kt22
-rw-r--r--packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/section/CommunalToDreamButtonSection.kt231
-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/notifications/ui/composable/row/BundleHeader.kt193
-rw-r--r--packages/SystemUI/compose/features/src/com/android/systemui/notifications/ui/composable/row/NotificationRowPrimitives.kt201
-rw-r--r--packages/SystemUI/compose/features/src/com/android/systemui/qs/footer/ui/compose/FooterActions.kt41
-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.kt51
-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.kt42
-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/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/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/goldens/verticalReveal_gesture_dragFullyClose.json654
-rw-r--r--packages/SystemUI/compose/scene/tests/goldens/verticalReveal_gesture_dragHalfClose.json644
-rw-r--r--packages/SystemUI/compose/scene/tests/goldens/verticalReveal_gesture_dragOpen.json544
-rw-r--r--packages/SystemUI/compose/scene/tests/goldens/verticalReveal_gesture_flingClose.json444
-rw-r--r--packages/SystemUI/compose/scene/tests/goldens/verticalReveal_gesture_flingOpen.json354
-rw-r--r--packages/SystemUI/compose/scene/tests/goldens/verticalReveal_gesture_magneticDetachAndReattach.json724
-rw-r--r--packages/SystemUI/compose/scene/tests/goldens/verticalReveal_triggeredRevealCloseTransition.json324
-rw-r--r--packages/SystemUI/compose/scene/tests/goldens/verticalReveal_triggeredRevealOpenTransition.json244
-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.kt279
-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.kt27
-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/ClockRegistry.kt17
-rw-r--r--packages/SystemUI/customization/src/com/android/systemui/shared/clocks/ComposedDigitalLayerController.kt11
-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.kt9
-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.kt10
-rw-r--r--packages/SystemUI/customization/src/com/android/systemui/shared/clocks/FlexClockFaceController.kt2
-rw-r--r--packages/SystemUI/customization/src/com/android/systemui/shared/clocks/SimpleClockLayerController.kt2
-rw-r--r--packages/SystemUI/customization/src/com/android/systemui/shared/clocks/SimpleDigitalHandLayerController.kt11
-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.kt52
-rw-r--r--packages/SystemUI/customization/src/com/android/systemui/shared/clocks/view/SimpleDigitalClockTextView.kt201
-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/KeyguardSecurityContainerControllerTest.kt33
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardSecurityContainerTest.java8
-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.java11
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/ambient/touch/BouncerFullscreenSwipeTouchHandlerTest.java18
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/ambient/touch/BouncerSwipeTouchHandlerTest.java76
-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.kt21
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/bluetooth/qsdialog/AudioSharingInteractorTest.kt4
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/bluetooth/qsdialog/AudioSharingRepositoryTest.kt12
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/domain/interactor/BouncerInteractorTest.kt80
-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.kt150
-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)22
-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/communal/CommunalSceneStartableTest.kt115
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/CommunalSceneInteractorTest.kt44
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/communal/ui/viewmodel/CommunalToDreamButtonViewModelTest.kt173
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/communal/view/viewmodel/CommunalViewModelTest.kt36
-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/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/KeyguardStateCallbackInteractorTest.kt3
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/GlanceableHubToPrimaryBouncerTransitionViewModelTest.kt97
-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/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.kt14
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/model/SysUiStateTest.java87
-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/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/tiles/impl/hearingdevices/domain/interactor/HearingDevicesTileUserActionInteractorTest.kt11
-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.java39
-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.kt50
-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.java32
-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.kt24
-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/ui/viewmodel/OngoingActivityChipsViewModelTest.kt42
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/ui/viewmodel/OngoingActivityChipsWithNotifsViewModelTest.kt312
-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/PhysicsPropertyAnimatorTest.kt7
-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/ConversationCoordinatorTest.kt39
-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/OriginalUnseenKeyguardCoordinatorTest.kt148
-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.kt3
-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/NotificationRowContentBinderImplTest.kt3
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/row/NotificationTestHelper.java27
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/row/ui/viewmodel/ActivatableNotificationViewModelTest.kt8
-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.kt59
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/StackStateAnimatorTest.kt111
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/ViewStateTest.kt4
-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/data/repository/MobileRepositorySwitcherKairosTest.kt173
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/demo/DemoMobileConnectionKairosParameterizedTest.kt285
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/demo/DemoMobileConnectionsRepositoryKairosTest.kt465
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/CarrierMergedConnectionRepositoryKairosTest.kt244
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/FullMobileConnectionRepositoryKairosTest.kt544
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionRepositoryKairosTest.kt1228
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionsRepositoryKairosTest.kt1350
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconInteractorKairosTest.kt760
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconsInteractorKairosTest.kt965
-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.kt8
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/HomeStatusBarViewModelImplTest.kt417
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/window/MultiDisplayStatusBarWindowControllerStoreTest.kt7
-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/plugin/src/com/android/systemui/plugins/clocks/ClockController.kt11
-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/ClockProviderPlugin.kt20
-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)14
-rw-r--r--packages/SystemUI/plugin/src/com/android/systemui/plugins/clocks/VRect.kt188
-rw-r--r--packages/SystemUI/plugin/src/com/android/systemui/plugins/clocks/WeatherData.kt70
-rw-r--r--packages/SystemUI/pods/com/android/systemui/dagger/qualifiers/BroadcastRunning.java (renamed from packages/SystemUI/src/com/android/systemui/dagger/qualifiers/BroadcastRunning.java)0
-rw-r--r--packages/SystemUI/pods/com/android/systemui/dagger/qualifiers/Default.java (renamed from packages/SystemUI/src/com/android/systemui/dagger/qualifiers/Default.java)0
-rw-r--r--packages/SystemUI/pods/com/android/systemui/dagger/qualifiers/DisplayId.java (renamed from packages/SystemUI/src/com/android/systemui/dagger/qualifiers/DisplayId.java)0
-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/InstrumentationTest.java (renamed from packages/SystemUI/src/com/android/systemui/dagger/qualifiers/InstrumentationTest.java)0
-rw-r--r--packages/SystemUI/pods/com/android/systemui/dagger/qualifiers/LongRunning.java (renamed from packages/SystemUI/src/com/android/systemui/dagger/qualifiers/LongRunning.java)0
-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/NotifInflation.kt (renamed from packages/SystemUI/src/com/android/systemui/dagger/qualifiers/NotifInflation.kt)2
-rw-r--r--packages/SystemUI/pods/com/android/systemui/dagger/qualifiers/PerUser.java (renamed from packages/SystemUI/src/com/android/systemui/dagger/qualifiers/PerUser.java)0
-rw-r--r--packages/SystemUI/pods/com/android/systemui/dagger/qualifiers/RootView.java (renamed from packages/SystemUI/src/com/android/systemui/dagger/qualifiers/RootView.java)0
-rw-r--r--packages/SystemUI/pods/com/android/systemui/dagger/qualifiers/SystemUser.kt (renamed from packages/SystemUI/src/com/android/systemui/dagger/qualifiers/SystemUser.kt)5
-rw-r--r--packages/SystemUI/pods/com/android/systemui/dagger/qualifiers/TestHarness.java (renamed from packages/SystemUI/src/com/android/systemui/dagger/qualifiers/TestHarness.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/dimens.xml1
-rw-r--r--packages/SystemUI/res/drawable/media_output_dialog_seekbar_background.xml14
-rw-r--r--packages/SystemUI/res/drawable/notification_2025_smart_reply_button_background.xml35
-rw-r--r--packages/SystemUI/res/drawable/settingslib_track_off_background.xml1
-rw-r--r--packages/SystemUI/res/layout/clipboard_overlay.xml3
-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_output_list_item_advanced.xml2
-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_smart_action_button.xml35
-rw-r--r--packages/SystemUI/res/layout/notification_2025_smart_reply_button.xml36
-rw-r--r--packages/SystemUI/res/layout/volume_dialog.xml6
-rw-r--r--packages/SystemUI/res/layout/volume_dialog_top_section.xml3
-rw-r--r--packages/SystemUI/res/values-af/strings.xml27
-rw-r--r--packages/SystemUI/res/values-am/strings.xml27
-rw-r--r--packages/SystemUI/res/values-ar/strings.xml40
-rw-r--r--packages/SystemUI/res/values-as/strings.xml27
-rw-r--r--packages/SystemUI/res/values-az/strings.xml27
-rw-r--r--packages/SystemUI/res/values-b+sr+Latn/strings.xml18
-rw-r--r--packages/SystemUI/res/values-be/strings.xml27
-rw-r--r--packages/SystemUI/res/values-bg/strings.xml18
-rw-r--r--packages/SystemUI/res/values-bn/strings.xml29
-rw-r--r--packages/SystemUI/res/values-bs/strings.xml24
-rw-r--r--packages/SystemUI/res/values-ca/strings.xml18
-rw-r--r--packages/SystemUI/res/values-cs/strings.xml27
-rw-r--r--packages/SystemUI/res/values-da/strings.xml27
-rw-r--r--packages/SystemUI/res/values-de/strings.xml27
-rw-r--r--packages/SystemUI/res/values-el/strings.xml18
-rw-r--r--packages/SystemUI/res/values-en-rAU/strings.xml27
-rw-r--r--packages/SystemUI/res/values-en-rCA/strings.xml5
-rw-r--r--packages/SystemUI/res/values-en-rGB/strings.xml27
-rw-r--r--packages/SystemUI/res/values-en-rIN/strings.xml27
-rw-r--r--packages/SystemUI/res/values-es-rUS/strings.xml18
-rw-r--r--packages/SystemUI/res/values-es/strings.xml29
-rw-r--r--packages/SystemUI/res/values-et/strings.xml27
-rw-r--r--packages/SystemUI/res/values-eu/strings.xml28
-rw-r--r--packages/SystemUI/res/values-fa/strings.xml18
-rw-r--r--packages/SystemUI/res/values-fi/strings.xml27
-rw-r--r--packages/SystemUI/res/values-fr-rCA/strings.xml27
-rw-r--r--packages/SystemUI/res/values-fr-rCA/tiles_states_strings.xml2
-rw-r--r--packages/SystemUI/res/values-fr/strings.xml18
-rw-r--r--packages/SystemUI/res/values-gl/strings.xml18
-rw-r--r--packages/SystemUI/res/values-gu/strings.xml18
-rw-r--r--packages/SystemUI/res/values-hi/strings.xml29
-rw-r--r--packages/SystemUI/res/values-hr/strings.xml18
-rw-r--r--packages/SystemUI/res/values-hu/strings.xml27
-rw-r--r--packages/SystemUI/res/values-hy/strings.xml18
-rw-r--r--packages/SystemUI/res/values-in/strings.xml27
-rw-r--r--packages/SystemUI/res/values-is/strings.xml18
-rw-r--r--packages/SystemUI/res/values-it/strings.xml18
-rw-r--r--packages/SystemUI/res/values-iw/strings.xml40
-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.xml31
-rw-r--r--packages/SystemUI/res/values-km/strings.xml18
-rw-r--r--packages/SystemUI/res/values-kn/strings.xml18
-rw-r--r--packages/SystemUI/res/values-ko/strings.xml29
-rw-r--r--packages/SystemUI/res/values-ky/strings.xml27
-rw-r--r--packages/SystemUI/res/values-lo/strings.xml18
-rw-r--r--packages/SystemUI/res/values-lt/strings.xml18
-rw-r--r--packages/SystemUI/res/values-lv/strings.xml18
-rw-r--r--packages/SystemUI/res/values-mk/strings.xml27
-rw-r--r--packages/SystemUI/res/values-ml/strings.xml18
-rw-r--r--packages/SystemUI/res/values-mn/strings.xml27
-rw-r--r--packages/SystemUI/res/values-mr/strings.xml30
-rw-r--r--packages/SystemUI/res/values-ms/strings.xml18
-rw-r--r--packages/SystemUI/res/values-my/strings.xml27
-rw-r--r--packages/SystemUI/res/values-nb/strings.xml27
-rw-r--r--packages/SystemUI/res/values-ne/strings.xml27
-rw-r--r--packages/SystemUI/res/values-nl/strings.xml27
-rw-r--r--packages/SystemUI/res/values-or/strings.xml27
-rw-r--r--packages/SystemUI/res/values-pa/strings.xml18
-rw-r--r--packages/SystemUI/res/values-pl/strings.xml33
-rw-r--r--packages/SystemUI/res/values-pt-rBR/strings.xml29
-rw-r--r--packages/SystemUI/res/values-pt-rPT/strings.xml18
-rw-r--r--packages/SystemUI/res/values-pt/strings.xml29
-rw-r--r--packages/SystemUI/res/values-ro/strings.xml27
-rw-r--r--packages/SystemUI/res/values-ru/strings.xml18
-rw-r--r--packages/SystemUI/res/values-si/strings.xml27
-rw-r--r--packages/SystemUI/res/values-sk/strings.xml27
-rw-r--r--packages/SystemUI/res/values-sl/strings.xml27
-rw-r--r--packages/SystemUI/res/values-sq/strings.xml27
-rw-r--r--packages/SystemUI/res/values-sr/strings.xml18
-rw-r--r--packages/SystemUI/res/values-sv/strings.xml18
-rw-r--r--packages/SystemUI/res/values-sw/strings.xml27
-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.xml27
-rw-r--r--packages/SystemUI/res/values-te/strings.xml18
-rw-r--r--packages/SystemUI/res/values-th/strings.xml27
-rw-r--r--packages/SystemUI/res/values-tl/strings.xml18
-rw-r--r--packages/SystemUI/res/values-tr/strings.xml27
-rw-r--r--packages/SystemUI/res/values-uk/strings.xml33
-rw-r--r--packages/SystemUI/res/values-ur/strings.xml18
-rw-r--r--packages/SystemUI/res/values-uz/strings.xml27
-rw-r--r--packages/SystemUI/res/values-vi/strings.xml29
-rw-r--r--packages/SystemUI/res/values-zh-rCN/strings.xml27
-rw-r--r--packages/SystemUI/res/values-zh-rHK/strings.xml18
-rw-r--r--packages/SystemUI/res/values-zh-rTW/strings.xml18
-rw-r--r--packages/SystemUI/res/values-zu/strings.xml27
-rw-r--r--packages/SystemUI/res/values/config.xml4
-rw-r--r--packages/SystemUI/res/values/dimens.xml24
-rw-r--r--packages/SystemUI/res/values/strings.xml15
-rw-r--r--packages/SystemUI/res/values/styles.xml52
-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.kt43
-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/KeyguardSecurityContainer.java19
-rw-r--r--packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java24
-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.java4
-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/AmbientVolumeLayout.java11
-rw-r--r--packages/SystemUI/src/com/android/systemui/accessibility/hearingaid/AmbientVolumeSlider.java12
-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.java46
-rw-r--r--packages/SystemUI/src/com/android/systemui/accessibility/hearingaid/HearingDevicesUiEvent.kt4
-rw-r--r--packages/SystemUI/src/com/android/systemui/ambient/touch/BouncerSwipeTouchHandler.kt94
-rw-r--r--packages/SystemUI/src/com/android/systemui/ambient/touch/scrim/BouncerScrimController.java4
-rw-r--r--packages/SystemUI/src/com/android/systemui/ambient/touch/scrim/ScrimController.java3
-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/ui/binder/CredentialPasswordViewBinder.kt3
-rw-r--r--packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/SideFpsOverlayViewBinder.kt11
-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/AudioSharingDeviceItemActionInteractorImpl.kt8
-rw-r--r--packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/AudioSharingInteractor.kt7
-rw-r--r--packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/AudioSharingRepository.kt15
-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/BluetoothTileDialogLogger.kt74
-rw-r--r--packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/DeviceItemFactory.kt52
-rw-r--r--packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/dagger/AudioSharingModule.kt3
-rw-r--r--packages/SystemUI/src/com/android/systemui/bouncer/domain/interactor/BouncerInteractor.kt33
-rw-r--r--packages/SystemUI/src/com/android/systemui/bouncer/ui/binder/BouncerViewBinder.kt4
-rw-r--r--packages/SystemUI/src/com/android/systemui/bouncer/ui/binder/KeyguardBouncerViewBinder.kt18
-rw-r--r--packages/SystemUI/src/com/android/systemui/brightness/ui/compose/BrightnessSlider.kt51
-rw-r--r--packages/SystemUI/src/com/android/systemui/clipboardoverlay/ActionIntentCreator.kt99
-rw-r--r--packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardOverlayController.java32
-rw-r--r--packages/SystemUI/src/com/android/systemui/clipboardoverlay/DefaultIntentCreator.java102
-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/communal/CommunalSceneStartable.kt29
-rw-r--r--packages/SystemUI/src/com/android/systemui/communal/DeviceInactiveCondition.java2
-rw-r--r--packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalSceneInteractor.kt15
-rw-r--r--packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalToDreamButtonViewModel.kt124
-rw-r--r--packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalViewModel.kt19
-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/dagger/qualifiers/AdditionalStartable.java0
-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/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/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/PerDisplayRepository.kt210
-rw-r--r--packages/SystemUI/src/com/android/systemui/display/data/repository/PerDisplayStore.kt9
-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/flags/RefactorFlagUtils.kt5
-rw-r--r--packages/SystemUI/src/com/android/systemui/grid/ui/compose/SpannedGrids.kt141
-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/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/KeyguardSliceProvider.java2
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java44
-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.kt2
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardRepository.kt4
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardClockInteractor.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/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.kt99
-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/view/layout/sections/SmartspaceSection.kt6
-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/GlanceableHubToPrimaryBouncerTransitionViewModel.kt19
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardClockViewModel.kt2
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModel.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/log/table/Diffable.kt96
-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/controls/domain/pipeline/LegacyMediaDataManagerImpl.kt2
-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.kt2
-rw-r--r--packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/MediaDataProcessor.kt2
-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/controller/MediaCarouselController.kt196
-rw-r--r--packages/SystemUI/src/com/android/systemui/media/controls/ui/controller/MediaCarouselControllerLogger.kt27
-rw-r--r--packages/SystemUI/src/com/android/systemui/media/controls/ui/controller/MediaControlPanel.java494
-rw-r--r--packages/SystemUI/src/com/android/systemui/media/controls/ui/controller/MediaHierarchyManager.kt21
-rw-r--r--packages/SystemUI/src/com/android/systemui/media/controls/ui/controller/MediaViewController.kt79
-rw-r--r--packages/SystemUI/src/com/android/systemui/media/controls/ui/controller/MediaViewLogger.kt28
-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/MediaHost.kt5
-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/MediaRecommendationsViewModel.kt238
-rw-r--r--packages/SystemUI/src/com/android/systemui/media/controls/ui/viewmodel/SeekBarViewModel.kt2
-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.java84
-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.java128
-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.kt159
-rw-r--r--packages/SystemUI/src/com/android/systemui/media/remedia/ui/compose/Media.kt1152
-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.kt66
-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.kt253
-rw-r--r--packages/SystemUI/src/com/android/systemui/model/SceneContainerPlugin.kt16
-rw-r--r--packages/SystemUI/src/com/android/systemui/model/SysUIStateChange.kt98
-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.kt212
-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/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.kt116
-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/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/BounceableInfo.kt11
-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.kt15
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/QuickQuickSettings.kt24
-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.kt90
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/infinitegrid/InfiniteGridLayout.kt32
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/infinitegrid/Tile.kt273
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/selection/Selection.kt59
-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/shared/QSPipelineFlagsRepository.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/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.kt2
-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.java74
-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.kt8
-rw-r--r--packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java57
-rw-r--r--packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowControllerImpl.java24
-rw-r--r--packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowState.kt7
-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/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.kt4
-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/KeyboardShortcutListSearch.java3
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/NotificationGroupingUtil.java56
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerImpl.java85
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/NotificationRemoteInputManager.java91
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/NotificationShadeDepthController.kt112
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/NotificationShadeWindowController.java3
-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.kt6
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/chips/ui/binder/OngoingActivityChipBinder.kt16
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/chips/ui/compose/ChipContent.kt27
-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.kt9
-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.kt19
-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/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.kt16
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/BundleEntry.java141
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/BundleEntryAdapter.kt131
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/EntryAdapter.java12
-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.java136
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationEntryAdapter.kt151
-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.java34
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/BundleCoordinator.kt20
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/ConversationCoordinator.kt13
-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.java15
-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/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/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/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.kt9
-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/PromotedNotificationsInteractor.kt21
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ActivatableNotificationView.java33
-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.java217
-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.kt11
-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.java26
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentInflater.java12
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationRowContentBinderImpl.kt45
-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/ui/viewbinder/ActivatableNotificationViewBinder.kt9
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ui/viewmodel/ActivatableNotificationViewModel.kt16
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ui/viewmodel/BundleHeaderViewModel.kt87
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationTemplateViewWrapper.java18
-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/AnimationProperties.java2
-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.java133
-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.java4
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackStateAnimator.java24
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ViewState.java5
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/view/NotificationScrollView.kt3
-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/viewbinder/NotificationScrollViewBinder.kt2
-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.kt21
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModel.kt35
-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/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/battery/domain/interactor/BatteryInteractor.kt6
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/pipeline/dagger/StatusBarPipelineModule.kt56
-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/MobileConnectionRepositoryKairos.kt178
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileConnectionsRepositoryKairos.kt122
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileConnectionsRepositoryKairosAdapter.kt165
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileRepositorySwitcher.kt6
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileRepositorySwitcherKairos.kt171
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/demo/DemoMobileConnectionRepositoryKairos.kt270
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/demo/DemoMobileConnectionsRepositoryKairos.kt255
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/demo/DemoModeMobileConnectionDataSourceKairos.kt157
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/demo/model/FakeNetworkEventModel.kt11
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/CarrierMergedConnectionRepositoryKairos.kt208
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/FullMobileConnectionRepositoryKairos.kt254
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionRepositoryImpl.kt37
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionRepositoryKairosAdapter.kt97
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionRepositoryKairosImpl.kt485
-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/data/repository/prod/MobileConnectionsRepositoryKairosImpl.kt584
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconInteractorKairos.kt355
-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.kt449
-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/shared/ui/binder/HomeStatusBarViewBinder.kt196
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/composable/StatusBarRoot.kt12
-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.kt122
-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/domain/interactor/ZenModeInteractor.kt2
-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/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/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/util/kotlin/Producer.kt22
-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/wallpapers/GradientColorWallpaper.kt31
-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/ClockEventControllerTest.kt2
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationControllerTest.java2
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/animation/TransitionAnimatorTest.kt90
-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/bluetooth/qsdialog/AudioSharingDeviceItemActionInteractorTest.kt10
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/bluetooth/qsdialog/DeviceItemFactoryTest.kt72
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/clipboardoverlay/ClipboardOverlayControllerTest.java14
-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/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/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/qs/panels/ui/compose/EditModeTest.kt13
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/qs/panels/ui/compose/ResizingTest.kt21
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/recents/LauncherProxyServiceTest.kt22
-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.kt33
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/shade/ShadeHeaderControllerTest.kt18
-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/events/SystemStatusAnimationSchedulerImplTest.kt58
-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/row/ExpandableNotificationRowTest.java62
-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/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/pipeline/mobile/data/model/SystemUiCarrierConfigTest.kt32
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/CarrierConfigRepositoryImplTest.kt8
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionRepositoryTest.kt18
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionTelephonySmokeTests.kt22
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/RemoteInputViewTest.java11
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/wallpapers/GradientColorWallpaperTest.kt20
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java16
-rw-r--r--packages/SystemUI/tests/utils/src/android/net/ConnectivityManagerKosmos.kt (renamed from packages/SystemUI/src/com/android/systemui/media/controls/ui/viewmodel/MediaRecsCardViewModel.kt)17
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/TestMocksModule.kt2
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/bluetooth/qsdialog/AudioSharingInteractorKosmos.kt1
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/bluetooth/qsdialog/FakeAudioSharingRepository.kt2
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/bouncer/domain/interactor/BouncerInteractorKosmos.kt2
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/communal/ui/viewmodel/CommunalToDreamButtonViewModelKosmos.kt39
-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.kt23
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/display/data/repository/PerDisplayStoreKosmos.kt29
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/kairos/CollectLastValue.kt106
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/kairos/KairosKosmos.kt69
-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/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/GlanceableHubToPrimaryBouncerTransitionViewModelKosmos.kt4
-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.kt12
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/log/LogAssert.kt114
-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/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/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/row/ui/viewmodel/ActivatableNotificationViewModelKosmos.kt2
-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/stack/ui/viewmodel/NotificationScrollViewModelKosmos.kt2
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModelKosmos.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/data/model/SystemUiCarrierConfigFakes.kt41
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/FakeMobileConnectionRepositoryKairos.kt87
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/FakeMobileConnectionsRepositoryKairos.kt134
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileDataRepositoryKairosKosmos.kt149
-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/mobile/util/FakeSubscriptionManagerProxy.kt (renamed from packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/mobile/util/FakeSubscriptionManagerProxy.kt)7
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/pipeline/shared/data/repository/ConnectivityRepositoryKosmos.kt2
-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/statusbar/pipeline/wifi/data/repository/WifiRepositoryKosmos.kt2
-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/util/CarrierConfigTrackerKosmos.kt (renamed from packages/SystemUI/src/com/android/systemui/media/controls/ui/viewmodel/MediaRecViewModel.kt)19
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/util/mockito/MockitoKosmos.kt22
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/volume/dialog/sliders/dagger/VolumeDialogSliderComponentKosmos.kt5
-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--proto/src/metrics_constants/OWNERS2
-rw-r--r--ravenwood/junit-src/android/platform/test/ravenwood/RavenwoodUtils.java22
-rw-r--r--ravenwood/runtime-helper-src/libcore-fake/libcore/util/NonNull.java12
-rw-r--r--ravenwood/runtime-helper-src/libcore-fake/libcore/util/Nullable.java12
-rwxr-xr-xravenwood/scripts/extract-last-soong-commands.py2
-rwxr-xr-xravenwood/scripts/list-ravenwood-tests.sh18
-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)0
-rw-r--r--ravenwood/tools/hoststubgen/lib/com/android/hoststubgen/HostStubGenClassProcessor.kt165
-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)29
-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)0
-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)0
-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)0
-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)0
-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)7
-rw-r--r--ravenwood/tools/hoststubgen/lib/com/android/hoststubgen/filters/TextFilePolicyMethodReplaceFilter.kt (renamed from ravenwood/tools/hoststubgen/src/com/android/hoststubgen/filters/TextFilePolicyMethodReplaceFilter.kt)0
-rw-r--r--ravenwood/tools/hoststubgen/lib/com/android/hoststubgen/filters/TextFilePolicyRemapperFilter.kt (renamed from ravenwood/tools/hoststubgen/src/com/android/hoststubgen/filters/TextFilePolicyRemapperFilter.kt)0
-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.kt206
-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)0
-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)101
-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.kt413
-rw-r--r--ravenwood/tools/hoststubgen/test-tiny-framework/golden-output.RELEASE_TARGET_JAVA_21/01-hoststubgen-test-tiny-framework-orig-dump.txt77
-rw-r--r--ravenwood/tools/hoststubgen/test-tiny-framework/golden-output.RELEASE_TARGET_JAVA_21/03-hoststubgen-test-tiny-framework-host-dump.txt116
-rw-r--r--ravenwood/tools/hoststubgen/test-tiny-framework/golden-output.RELEASE_TARGET_JAVA_21/13-hoststubgen-test-tiny-framework-host-ext-dump.txt150
-rw-r--r--ravenwood/tools/hoststubgen/test-tiny-framework/golden-output/01-hoststubgen-test-tiny-framework-orig-dump.txt77
-rw-r--r--ravenwood/tools/hoststubgen/test-tiny-framework/golden-output/03-hoststubgen-test-tiny-framework-host-dump.txt116
-rw-r--r--ravenwood/tools/hoststubgen/test-tiny-framework/golden-output/13-hoststubgen-test-tiny-framework-host-ext-dump.txt150
-rw-r--r--ravenwood/tools/hoststubgen/test-tiny-framework/policy-override-tiny-framework.txt4
-rw-r--r--ravenwood/tools/hoststubgen/test-tiny-framework/tiny-framework/src/com/android/hoststubgen/test/tinyframework/TinyFrameworkMethodCallReplace.java17
-rw-r--r--ravenwood/tools/hoststubgen/test-tiny-framework/tiny-test/src/com/android/hoststubgen/test/tinyframework/TinyFrameworkClassTest.java6
-rw-r--r--ravenwood/tools/ravenhelper/Android.bp6
-rw-r--r--ravenwood/tools/ravenhelper/src/com/android/platform/test/ravenwood/ravenhelper/policytoannot/PtaOptions.kt88
-rw-r--r--ravenwood/tools/ravenhelper/src/com/android/platform/test/ravenwood/ravenhelper/policytoannot/PtaProcessor.kt3
-rw-r--r--ravenwood/tools/ravenhelper/src/com/android/platform/test/ravenwood/ravenhelper/sourcemap/MapOptions.kt82
-rw-r--r--ravenwood/tools/ravenhelper/src/com/android/platform/test/ravenwood/ravenhelper/sourcemap/MarkMethodHandler.kt2
-rw-r--r--ravenwood/tools/ravenizer/Android.bp6
-rw-r--r--ravenwood/tools/ravenizer/src/com/android/platform/test/ravenwood/ravenizer/RavenizerMain.kt25
-rw-r--r--ravenwood/tools/ravenizer/src/com/android/platform/test/ravenwood/ravenizer/RavenizerOptions.kt123
-rw-r--r--services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java3
-rw-r--r--services/accessibility/java/com/android/server/accessibility/autoclick/AutoclickController.java81
-rw-r--r--services/accessibility/java/com/android/server/accessibility/autoclick/AutoclickLinearLayout.java80
-rw-r--r--services/accessibility/java/com/android/server/accessibility/autoclick/AutoclickScrollPanel.java105
-rw-r--r--services/accessibility/java/com/android/server/accessibility/autoclick/AutoclickTypePanel.java54
-rw-r--r--services/accessibility/java/com/android/server/accessibility/gestures/TouchExplorer.java3
-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/Session.java10
-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.java44
-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/core/Android.bp8
-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.java38
-rw-r--r--services/core/java/com/android/server/am/ActiveServices.java11
-rw-r--r--services/core/java/com/android/server/am/ActivityManagerService.java18
-rw-r--r--services/core/java/com/android/server/am/BroadcastController.java4
-rw-r--r--services/core/java/com/android/server/am/BroadcastProcessQueue.java4
-rw-r--r--services/core/java/com/android/server/am/BroadcastProcessedEventRecord.java142
-rw-r--r--services/core/java/com/android/server/am/BroadcastQueueImpl.java24
-rw-r--r--services/core/java/com/android/server/am/BroadcastRecord.java61
-rw-r--r--services/core/java/com/android/server/am/OomAdjuster.java8
-rw-r--r--services/core/java/com/android/server/am/PendingIntentRecord.java3
-rw-r--r--services/core/java/com/android/server/am/ProcessRecord.java21
-rw-r--r--services/core/java/com/android/server/am/SettingsToPropertiesMapper.java7
-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.java49
-rw-r--r--services/core/java/com/android/server/appop/AppOpsUidStateTrackerImpl.java10
-rw-r--r--services/core/java/com/android/server/appop/AttributedOp.java11
-rw-r--r--services/core/java/com/android/server/appop/DiscreteOpsDbHelper.java8
-rw-r--r--services/core/java/com/android/server/appop/DiscreteOpsMigrationHelper.java11
-rw-r--r--services/core/java/com/android/server/appop/DiscreteOpsRegistry.java156
-rw-r--r--services/core/java/com/android/server/appop/DiscreteOpsSqlRegistry.java131
-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.java10
-rw-r--r--services/core/java/com/android/server/audio/AudioDeviceBroker.java177
-rw-r--r--services/core/java/com/android/server/audio/AudioDeviceInventory.java29
-rw-r--r--services/core/java/com/android/server/audio/AudioService.java3
-rw-r--r--services/core/java/com/android/server/clipboard/OWNERS2
-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/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.java15
-rw-r--r--services/core/java/com/android/server/display/VirtualDisplayAdapter.java92
-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.java3
-rw-r--r--services/core/java/com/android/server/dreams/DreamManagerService.java15
-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/InputDataStore.java4
-rw-r--r--services/core/java/com/android/server/input/InputGestureManager.java5
-rw-r--r--services/core/java/com/android/server/inputmethod/DefaultImeVisibilityApplier.java24
-rw-r--r--services/core/java/com/android/server/inputmethod/ImeProtoLogGroup.java6
-rw-r--r--services/core/java/com/android/server/inputmethod/ImeVisibilityStateComputer.java38
-rw-r--r--services/core/java/com/android/server/inputmethod/InputMethodManagerService.java7
-rw-r--r--services/core/java/com/android/server/inputmethod/OWNERS1
-rw-r--r--services/core/java/com/android/server/location/contexthub/ContextHubEndpointBroker.java151
-rw-r--r--services/core/java/com/android/server/locksettings/LockSettingsService.java6
-rw-r--r--services/core/java/com/android/server/locksettings/recoverablekeystore/OWNERS1
-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.java85
-rw-r--r--services/core/java/com/android/server/media/quality/MediaQualityUtils.java7
-rw-r--r--services/core/java/com/android/server/notification/GroupHelper.java103
-rw-r--r--services/core/java/com/android/server/notification/NotificationManagerService.java57
-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.java37
-rw-r--r--services/core/java/com/android/server/om/OverlayManagerServiceImpl.java4
-rw-r--r--services/core/java/com/android/server/os/instrumentation/OWNERS1
-rw-r--r--services/core/java/com/android/server/pm/BackgroundInstallControlService.java7
-rw-r--r--services/core/java/com/android/server/pm/InstallPackageHelper.java4
-rw-r--r--services/core/java/com/android/server/pm/InstallRequest.java26
-rw-r--r--services/core/java/com/android/server/pm/PackageInstallerSession.java52
-rw-r--r--services/core/java/com/android/server/pm/PackageMetrics.java4
-rw-r--r--services/core/java/com/android/server/pm/ShortcutService.java2
-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.java8
-rw-r--r--services/core/java/com/android/server/policy/WindowManagerPolicy.java14
-rw-r--r--services/core/java/com/android/server/power/PowerManagerService.java7
-rw-r--r--services/core/java/com/android/server/power/ScreenUndimDetector.java44
-rw-r--r--services/core/java/com/android/server/power/stats/BatteryHistoryDirectory.java42
-rw-r--r--services/core/java/com/android/server/security/advancedprotection/OWNERS1
-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/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.java81
-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.java54
-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.java285
-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/ActivityStarter.java22
-rw-r--r--services/core/java/com/android/server/wm/ActivityTaskSupervisor.java25
-rw-r--r--services/core/java/com/android/server/wm/AppCompatController.java8
-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/AppCompatSizeCompatModePolicy.java9
-rw-r--r--services/core/java/com/android/server/wm/AppCompatUtils.java3
-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.java111
-rw-r--r--services/core/java/com/android/server/wm/BackgroundLaunchProcessController.java10
-rw-r--r--services/core/java/com/android/server/wm/BaseAppSnapshotPersister.java84
-rw-r--r--services/core/java/com/android/server/wm/ClientLifecycleManager.java102
-rw-r--r--services/core/java/com/android/server/wm/ContentRecorder.java2
-rw-r--r--services/core/java/com/android/server/wm/DeferredDisplayUpdater.java26
-rw-r--r--services/core/java/com/android/server/wm/DesktopModeBoundsCalculator.java96
-rw-r--r--services/core/java/com/android/server/wm/DesktopModeHelper.java10
-rw-r--r--services/core/java/com/android/server/wm/DesktopModeLaunchParamsModifier.java63
-rw-r--r--services/core/java/com/android/server/wm/DisplayContent.java122
-rw-r--r--services/core/java/com/android/server/wm/DisplayPolicy.java64
-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.java15
-rw-r--r--services/core/java/com/android/server/wm/InsetsPolicy.java1
-rw-r--r--services/core/java/com/android/server/wm/InsetsSourceProvider.java19
-rw-r--r--services/core/java/com/android/server/wm/KeyguardController.java42
-rw-r--r--services/core/java/com/android/server/wm/Letterbox.java23
-rw-r--r--services/core/java/com/android/server/wm/RootWindowContainer.java48
-rw-r--r--services/core/java/com/android/server/wm/SurfaceAnimationRunner.java269
-rw-r--r--services/core/java/com/android/server/wm/Task.java43
-rw-r--r--services/core/java/com/android/server/wm/TaskDisplayArea.java56
-rw-r--r--services/core/java/com/android/server/wm/TaskFragment.java348
-rw-r--r--services/core/java/com/android/server/wm/Transition.java8
-rw-r--r--services/core/java/com/android/server/wm/TransitionController.java13
-rw-r--r--services/core/java/com/android/server/wm/ViewServer.java9
-rw-r--r--services/core/java/com/android/server/wm/WallpaperController.java92
-rw-r--r--services/core/java/com/android/server/wm/WindowChangeAnimationSpec.java200
-rw-r--r--services/core/java/com/android/server/wm/WindowContainer.java13
-rw-r--r--services/core/java/com/android/server/wm/WindowManagerInternal.java31
-rw-r--r--services/core/java/com/android/server/wm/WindowManagerService.java141
-rw-r--r--services/core/java/com/android/server/wm/WindowOrganizerController.java45
-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.java2
-rw-r--r--services/core/java/com/android/server/wm/WindowStateAnimator.java2
-rw-r--r--services/core/jni/Android.bp4
-rw-r--r--services/core/jni/com_android_server_display_DisplayControl.cpp7
-rw-r--r--services/core/jni/com_android_server_vibrator_VibratorController.cpp28
-rw-r--r--services/core/jni/stats/OWNERS2
-rw-r--r--services/credentials/java/com/android/server/credentials/CredentialManagerService.java30
-rw-r--r--services/devicepolicy/java/com/android/server/devicepolicy/ActiveAdmin.java8
-rw-r--r--services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java166
-rw-r--r--services/devicepolicy/java/com/android/server/devicepolicy/OwnersData.java9
-rw-r--r--services/foldables/devicestateprovider/src/com/android/server/policy/FoldableDeviceStateProvider.java6
-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/supervision/java/com/android/server/supervision/SupervisionService.java56
-rw-r--r--services/tests/DynamicInstrumentationManagerServiceTests/OWNERS1
-rw-r--r--services/tests/InputMethodSystemServerTests/src/com/android/inputmethodservice/AndroidTest.xml2
-rw-r--r--services/tests/InputMethodSystemServerTests/src/com/android/inputmethodservice/InputMethodServiceTest.java10
-rw-r--r--services/tests/InputMethodSystemServerTests/test-apps/SimpleTestIme/AndroidManifest.xml3
-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/DisplayTopologyCoordinatorTest.kt36
-rw-r--r--services/tests/displayservicetests/src/com/android/server/display/VirtualDisplayAdapterTest.java119
-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/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/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/BroadcastRecordTest.java156
-rw-r--r--services/tests/mockingservicestests/src/com/android/server/am/MockingOomAdjusterTests.java42
-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/crashrecovery/Android.bp4
-rw-r--r--services/tests/mockingservicestests/src/com/android/server/pm/UserManagerServiceMockedTest.java (renamed from services/tests/mockingservicestests/src/com/android/server/pm/UserManagerServiceTest.java)7
-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/wallpaper/WallpaperCropperTest.java101
-rw-r--r--services/tests/mockingservicestests/src/com/android/server/wallpaper/WallpaperDefaultDisplayInfoTest.java340
-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/TestApp/src/com/android/coretests/apps/testapp/Android.bp1
-rw-r--r--services/tests/servicestests/src/com/android/server/accessibility/autoclick/AutoclickControllerTest.java250
-rw-r--r--services/tests/servicestests/src/com/android/server/accessibility/autoclick/AutoclickLinearLayoutTest.java102
-rw-r--r--services/tests/servicestests/src/com/android/server/accessibility/autoclick/AutoclickScrollPanelTest.java92
-rw-r--r--services/tests/servicestests/src/com/android/server/accessibility/autoclick/AutoclickTypePanelTest.java69
-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.java24
-rw-r--r--services/tests/servicestests/src/com/android/server/accessibility/magnification/FullScreenMagnificationPointerMotionEventFilterTest.java79
-rw-r--r--services/tests/servicestests/src/com/android/server/appop/DiscreteAppOpSqlPersistenceTest.java4
-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.java7
-rw-r--r--services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java42
-rw-r--r--services/tests/servicestests/src/com/android/server/devicepolicy/DpmMockContext.java10
-rw-r--r--services/tests/servicestests/src/com/android/server/devicepolicy/MockSystemServices.java3
-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/location/contexthub/ContextHubEndpointTest.java69
-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/pm/UserManagerTest.java47
-rw-r--r--services/tests/servicestests/src/com/android/server/supervision/SupervisionServiceTest.kt8
-rw-r--r--services/tests/uiservicestests/src/com/android/server/notification/GroupHelperTest.java175
-rw-r--r--services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java88
-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.java143
-rw-r--r--services/tests/uiservicestests/src/com/android/server/notification/ZenModeHelperTest.java3
-rw-r--r--services/tests/vibrator/src/com/android/server/vibrator/VibrationThreadTest.java47
-rw-r--r--services/tests/wmtests/src/com/android/server/policy/KeyGestureEventTests.java2
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java28
-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.java5
-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/BackNavigationControllerTests.java15
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/BackgroundActivityStartControllerExemptionTests.java6
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/BackgroundActivityStartControllerLogTests.java18
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/BackgroundActivityStartControllerTests.java18
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/CameraCompatFreeformPolicyTests.java887
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/ClientLifecycleManagerTests.java76
-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.java144
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/DisplayCompatTests.java71
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java46
-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.java2
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/InsetsPolicyTest.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/RootWindowContainerTests.java19
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java38
-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/WindowOrganizerTests.java1
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/WindowProcessControllerTests.java3
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java15
-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/SubscriptionManager.java55
-rw-r--r--telephony/java/android/telephony/TelephonyManager.java11
-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/AppJankTest/src/android/app/jank/tests/JankTrackerTest.java16
-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/Android.bp1
-rw-r--r--tests/Input/src/com/android/server/input/BatteryControllerTests.kt4
-rw-r--r--tests/Input/src/com/android/server/input/InputManagerServiceTests.kt6
-rw-r--r--tests/Input/src/com/android/server/input/KeyGestureControllerTests.kt8
-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/Input/src/com/android/test/input/AnrTest.kt152
-rw-r--r--tests/Input/src/com/android/test/input/IAnrTestService.aidl17
-rw-r--r--tests/Input/src/com/android/test/input/UnresponsiveGestureMonitorActivity.kt25
-rw-r--r--tests/PackageWatchdog/Android.bp4
-rw-r--r--tests/UsbTests/src/com/android/server/usb/UsbServiceTest.java67
-rw-r--r--tests/vcn/java/com/android/server/VcnManagementServiceTest.java17
-rw-r--r--tools/codegen/OWNERS1
-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/OWNERS1
-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/java/src/android/net/wifi/sharedconnectivity/OWNERS1
2267 files changed, 73597 insertions, 24648 deletions
diff --git a/AconfigFlags.bp b/AconfigFlags.bp
index 8bfac03060b5..2dd16de3e188 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",
@@ -1943,3 +1944,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/apct-tests/perftests/core/src/android/input/MotionPredictorBenchmark.kt b/apct-tests/perftests/core/src/android/input/MotionPredictorBenchmark.kt
index add0a086201b..cf93331d87c2 100644
--- a/apct-tests/perftests/core/src/android/input/MotionPredictorBenchmark.kt
+++ b/apct-tests/perftests/core/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/core/src/android/input/VelocityTrackerBenchmarkTest.kt b/apct-tests/perftests/core/src/android/input/VelocityTrackerBenchmarkTest.kt
index 530ca7b7e5a0..c6fe32467099 100644
--- a/apct-tests/perftests/core/src/android/input/VelocityTrackerBenchmarkTest.kt
+++ b/apct-tests/perftests/core/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.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/core/src/android/os/TracePerfTest.java b/apct-tests/perftests/core/src/android/os/TracePerfTest.java
index d9051240d399..00e1c1fdbf4b 100644
--- a/apct-tests/perftests/core/src/android/os/TracePerfTest.java
+++ b/apct-tests/perftests/core/src/android/os/TracePerfTest.java
@@ -147,7 +147,7 @@ public class TracePerfTest {
.addField(1 /* sending_thread_name */, "foo")
.endNested()
.endProto()
- .addTerminatingFlow(5)
+ .setTerminatingFlow(5)
.emit();
BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
@@ -158,7 +158,7 @@ public class TracePerfTest {
.addField(1 /* sending_thread_name */, "foo")
.endNested()
.endProto()
- .addTerminatingFlow(5)
+ .setTerminatingFlow(5)
.emit();
}
}
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/OWNERS b/apct-tests/perftests/healthconnect/OWNERS
index acfe799314ec..7c9e7692a9fc 100644
--- a/apct-tests/perftests/healthconnect/OWNERS
+++ b/apct-tests/perftests/healthconnect/OWNERS
@@ -1,5 +1,4 @@
# Bug component: 1219472
-arkivanov@google.com
jstembridge@google.com
itsleo@google.com
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/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/app_idle.aconfig b/apex/jobscheduler/service/aconfig/app_idle.aconfig
index 74d2a590086f..899341016148 100644
--- a/apex/jobscheduler/service/aconfig/app_idle.aconfig
+++ b/apex/jobscheduler/service/aconfig/app_idle.aconfig
@@ -28,3 +28,14 @@ flag {
description: "Adjust the default bucket evaluation parameters"
bug: "379909479"
}
+
+flag {
+ name: "persist_restore_to_rare_apps_list"
+ namespace: "backstage_power"
+ description: "Persist the list of apps which are put in the RARE bucket upon restore."
+ is_fixed_read_only: true
+ bug: "383766428"
+ metadata {
+ purpose: PURPOSE_BUGFIX
+ }
+}
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/usage/AppIdleHistory.java b/apex/jobscheduler/service/java/com/android/server/usage/AppIdleHistory.java
index a8641ae43509..4acfebc536eb 100644
--- a/apex/jobscheduler/service/java/com/android/server/usage/AppIdleHistory.java
+++ b/apex/jobscheduler/service/java/com/android/server/usage/AppIdleHistory.java
@@ -38,6 +38,7 @@ import android.app.usage.AppStandbyInfo;
import android.app.usage.UsageStatsManager;
import android.os.SystemClock;
import android.util.ArrayMap;
+import android.util.ArraySet;
import android.util.AtomicFile;
import android.util.IndentingPrintWriter;
import android.util.Slog;
@@ -83,6 +84,9 @@ public class AppIdleHistory {
private SparseArray<ArrayMap<String,AppUsageHistory>> mIdleHistory = new SparseArray<>();
private static final long ONE_MINUTE = 60 * 1000;
+ // Only keep the persisted restore-to-rare apps list for 2 days.
+ static final long RESTORE_TO_RARE_APPS_LIST_EXPIRY = ONE_MINUTE * 60 * 24 * 2;
+
static final int STANDBY_BUCKET_UNKNOWN = -1;
/**
@@ -277,6 +281,58 @@ public class AppIdleHistory {
writeScreenOnTime();
}
+ private File getRestoreToRareAppsListFile(int userId) {
+ return new File(getUserDirectory(userId), "restore_to_rare_apps_list");
+ }
+
+ public ArraySet<String> readRestoreToRareAppsList(int userId) {
+ File restoreToRareAppsListFile = getRestoreToRareAppsListFile(userId);
+ if (!restoreToRareAppsListFile.exists()) {
+ return null;
+ }
+
+ try (BufferedReader reader =
+ new BufferedReader(new FileReader(restoreToRareAppsListFile))) {
+ final ArraySet<String> appsList = new ArraySet<>();
+ final long restoreTime = Long.parseLong(reader.readLine());
+ if (System.currentTimeMillis() - restoreTime > RESTORE_TO_RARE_APPS_LIST_EXPIRY) {
+ // the apps list should only be kept around for 2 days
+ reader.close();
+ restoreToRareAppsListFile.delete();
+ return null;
+ }
+ String pkgName;
+ while ((pkgName = reader.readLine()) != null) {
+ appsList.add(pkgName);
+ }
+ return appsList;
+ } catch (IOException | NumberFormatException e) {
+ return null;
+ }
+ }
+
+ public void writeRestoreToRareAppsList(int userId, ArraySet<String> restoreAppsToRare) {
+ File fileHandle = getRestoreToRareAppsListFile(userId);
+ if (fileHandle.exists()) {
+ // don't update the persisted file - it should only be written once.
+ return;
+ }
+ AtomicFile restoreToRareAppsListFile = new AtomicFile(fileHandle);
+ FileOutputStream fos = null;
+ try {
+ fos = restoreToRareAppsListFile.startWrite();
+ final StringBuilder sb = new StringBuilder();
+ sb.append(System.currentTimeMillis()).append("\n");
+ for (String pkgName : restoreAppsToRare) {
+ sb.append(pkgName).append("\n");
+ }
+ fos.write(sb.toString().getBytes());
+ restoreToRareAppsListFile.finishWrite(fos);
+ } catch (IOException ioe) {
+ restoreToRareAppsListFile.failWrite(fos);
+ }
+ }
+
/**
* Mark the app as used and update the bucket if necessary. If there is a expiry time specified
* that's in the future, then the usage event is temporary and keeps the app in the specified
@@ -694,10 +750,13 @@ public class AppIdleHistory {
return appUsageHistory.bucketExpiryTimesMs.get(bucket, 0);
}
+ private File getUserDirectory(int userId) {
+ return new File(new File(mStorageDir, "users"), Integer.toString(userId));
+ }
+
@VisibleForTesting
File getUserFile(int userId) {
- return new File(new File(new File(mStorageDir, "users"),
- Integer.toString(userId)), APP_IDLE_FILENAME);
+ return new File(getUserDirectory(userId), APP_IDLE_FILENAME);
}
void clearLastUsedTimestamps(String packageName, int userId) {
diff --git a/apex/jobscheduler/service/java/com/android/server/usage/AppStandbyController.java b/apex/jobscheduler/service/java/com/android/server/usage/AppStandbyController.java
index ab8131ba5126..b87b5ceebd98 100644
--- a/apex/jobscheduler/service/java/com/android/server/usage/AppStandbyController.java
+++ b/apex/jobscheduler/service/java/com/android/server/usage/AppStandbyController.java
@@ -1706,10 +1706,18 @@ public class AppStandbyController
restoreAppToRare(packageName, userId, nowElapsed, reason);
}
// Clear out the list of restored apps that need to have their standby buckets adjusted
- // if they still haven't been installed eight hours after restore.
- // Note: if the device reboots within these first 8 hours, this list will be lost since it's
- // not persisted - this is the expected behavior for now and may be updated in the future.
- mHandler.postDelayed(() -> mAppsToRestoreToRare.remove(userId), 8 * ONE_HOUR);
+ // if they still haven't been installed two days after initial restore.
+ final long delayMillis = Flags.persistRestoreToRareAppsList()
+ ? AppIdleHistory.RESTORE_TO_RARE_APPS_LIST_EXPIRY : 8 * ONE_HOUR;
+ mHandler.postDelayed(() -> mAppsToRestoreToRare.remove(userId), delayMillis);
+
+ // Persist the file in case the device reboots within 2 days after the initial restore.
+ if (Flags.persistRestoreToRareAppsList()) {
+ synchronized (mAppIdleLock) {
+ mAppIdleHistory.writeRestoreToRareAppsList(
+ userId, mAppsToRestoreToRare.get(userId));
+ }
+ }
}
/** Adjust the standby bucket of the given package for the user to RARE. */
@@ -2272,6 +2280,22 @@ public class AppStandbyController
} else if (!Intent.ACTION_PACKAGE_ADDED.equals(action)) {
clearAppIdleForPackage(pkgName, userId);
} else {
+ // Do a lazy read of the persisted list, if necessary.
+ if (Flags.persistRestoreToRareAppsList()
+ && mAppsToRestoreToRare.get(userId) == null) {
+ synchronized (mAppIdleLock) {
+ final ArraySet<String> restoredApps =
+ mAppIdleHistory.readRestoreToRareAppsList(userId);
+ if (restoredApps != null) {
+ mAppsToRestoreToRare.addAll(userId, restoredApps);
+ // Clear out the list of restored apps if they still haven't been
+ // installed in two days - at worst, we are allowing for up to
+ // 4 days for reinstallation (device reboots just before 2 days)
+ mHandler.postDelayed(() -> mAppsToRestoreToRare.remove(userId),
+ AppIdleHistory.RESTORE_TO_RARE_APPS_LIST_EXPIRY);
+ }
+ }
+ }
// Package was just added and it's not being replaced.
if (mAppsToRestoreToRare.contains(userId, pkgName)) {
restoreAppToRare(pkgName, userId, mInjector.elapsedRealtime(),
@@ -2454,6 +2478,8 @@ public class AppStandbyController
+ ": " + Flags.avoidIdleCheck());
pw.println(" " + Flags.FLAG_ADJUST_DEFAULT_BUCKET_ELEVATION_PARAMS
+ ": " + Flags.adjustDefaultBucketElevationParams());
+ pw.println(" " + Flags.FLAG_PERSIST_RESTORE_TO_RARE_APPS_LIST
+ + ": " + Flags.persistRestoreToRareAppsList());
pw.println();
synchronized (mCarrierPrivilegedLock) {
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..ae150aece432 100644
--- a/cmds/bmgr/src/com/android/commands/bmgr/Bmgr.java
+++ b/cmds/bmgr/src/com/android/commands/bmgr/Bmgr.java
@@ -1309,6 +1309,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 13127537e6d9..1630d80346ce 100644
--- a/core/api/current.txt
+++ b/core/api/current.txt
@@ -16993,7 +16993,7 @@ package android.graphics {
method public void setFilterBitmap(boolean);
method public void setFlags(int);
method public void setFontFeatureSettings(String);
- method @FlaggedApi("com.android.text.flags.typeface_redesign_readonly") public boolean setFontVariationOverride(@Nullable String);
+ method @FlaggedApi("com.android.text.flags.typeface_redesign_readonly") public void setFontVariationOverride(@Nullable String);
method public boolean setFontVariationSettings(String);
method public void setHinting(int);
method public void setLetterSpacing(float);
@@ -23332,12 +23332,12 @@ package android.media {
public static final class MediaCodecInfo.CodecCapabilities {
ctor public MediaCodecInfo.CodecCapabilities();
method public static android.media.MediaCodecInfo.CodecCapabilities createFromProfileLevel(String, int, int);
- method public android.media.MediaCodecInfo.AudioCapabilities getAudioCapabilities();
+ method @Nullable public android.media.MediaCodecInfo.AudioCapabilities getAudioCapabilities();
method public android.media.MediaFormat getDefaultFormat();
- method public android.media.MediaCodecInfo.EncoderCapabilities getEncoderCapabilities();
+ method @Nullable public android.media.MediaCodecInfo.EncoderCapabilities getEncoderCapabilities();
method public int getMaxSupportedInstances();
method public String getMimeType();
- method public android.media.MediaCodecInfo.VideoCapabilities getVideoCapabilities();
+ method @Nullable public android.media.MediaCodecInfo.VideoCapabilities getVideoCapabilities();
method public boolean isFeatureRequired(String);
method public boolean isFeatureSupported(String);
method public boolean isFormatSupported(android.media.MediaFormat);
@@ -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/module-lib-current.txt b/core/api/module-lib-current.txt
index 526a213a6003..132c65cc26ee 100644
--- a/core/api/module-lib-current.txt
+++ b/core/api/module-lib-current.txt
@@ -410,6 +410,7 @@ package android.os {
method public void invalidateCache();
method public static void invalidateCache(@NonNull String, @NonNull String);
method @Nullable public Result query(@NonNull Query);
+ method @FlaggedApi("android.os.ipc_data_cache_test_apis") public static void setTestMode(boolean);
field public static final String MODULE_BLUETOOTH = "bluetooth";
}
diff --git a/core/api/system-current.txt b/core/api/system-current.txt
index 984bc680c685..32b170a6286b 100644
--- a/core/api/system-current.txt
+++ b/core/api/system-current.txt
@@ -11725,6 +11725,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";
}
diff --git a/core/api/test-current.txt b/core/api/test-current.txt
index 514a58244aa2..bb023f25094e 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);
@@ -2128,6 +2129,12 @@ package android.media {
method public android.media.PlaybackParams setAudioStretchMode(int);
}
+ public class Utils {
+ ctor public Utils();
+ field public static final String SYNCHRONIZED_VIBRATION = "synchronized";
+ field public static final String VIBRATION_URI_PARAM = "vibration_uri";
+ }
+
public final class VolumePolicy implements android.os.Parcelable {
ctor public VolumePolicy(boolean, boolean, boolean, int);
method public int describeContents();
@@ -2453,7 +2460,7 @@ package android.os {
method public static void invalidateCache(@NonNull String, @NonNull String);
method public final boolean isDisabled();
method @Nullable public Result query(@NonNull Query);
- method public static void setTestMode(boolean);
+ method @FlaggedApi("android.os.ipc_data_cache_test_apis") public static void setTestMode(boolean);
field public static final String MODULE_BLUETOOTH = "bluetooth";
field public static final String MODULE_SYSTEM = "system_server";
field public static final String MODULE_TEST = "test";
@@ -3025,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";
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..d5b2f980e1a6 100644
--- a/core/java/android/animation/AnimationHandler.java
+++ b/core/java/android/animation/AnimationHandler.java
@@ -384,6 +384,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 69d3e8d4c0d2..54ab3b8f185b 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;
@@ -550,35 +547,35 @@ public class ActivityManager {
public static final int START_ASSISTANT_NOT_ACTIVE_SESSION = FIRST_START_FATAL_ERROR_CODE + 11;
/**
- * Result for IActivityManaqer.startActivity: the activity was started
+ * Result for IActivityManager.startActivity: the activity was started
* successfully as normal.
* @hide
*/
public static final int START_SUCCESS = FIRST_START_SUCCESS_CODE;
/**
- * Result for IActivityManaqer.startActivity: the caller asked that the Intent not
+ * Result for IActivityManager.startActivity: the caller asked that the Intent not
* be executed if it is the recipient, and that is indeed the case.
* @hide
*/
public static final int START_RETURN_INTENT_TO_CALLER = FIRST_START_SUCCESS_CODE + 1;
/**
- * Result for IActivityManaqer.startActivity: activity was started or brought forward in an
+ * Result for IActivityManager.startActivity: activity was started or brought forward in an
* existing task which was brought to the foreground.
* @hide
*/
public static final int START_TASK_TO_FRONT = FIRST_START_SUCCESS_CODE + 2;
/**
- * Result for IActivityManaqer.startActivity: activity wasn't really started, but
+ * Result for IActivityManager.startActivity: activity wasn't really started, but
* the given Intent was given to the existing top activity.
* @hide
*/
public static final int START_DELIVERED_TO_TOP = FIRST_START_SUCCESS_CODE + 3;
/**
- * Result for IActivityManaqer.startActivity: request was canceled because
+ * Result for IActivityManager.startActivity: request was canceled because
* app switches are temporarily canceled to ensure the user's last request
* (such as pressing home) is performed.
* @hide
@@ -586,7 +583,7 @@ public class ActivityManager {
public static final int START_SWITCHES_CANCELED = FIRST_START_NON_FATAL_ERROR_CODE;
/**
- * Result for IActivityManaqer.startActivity: a new activity was attempted to be started
+ * Result for IActivityManager.startActivity: a new activity was attempted to be started
* while in Lock Task Mode.
* @hide
*/
@@ -594,55 +591,55 @@ public class ActivityManager {
FIRST_START_NON_FATAL_ERROR_CODE + 1;
/**
- * Result for IActivityManaqer.startActivity: a new activity start was aborted. Never returned
+ * Result for IActivityManager.startActivity: a new activity start was aborted. Never returned
* externally.
* @hide
*/
public static final int START_ABORTED = FIRST_START_NON_FATAL_ERROR_CODE + 2;
/**
- * Flag for IActivityManaqer.startActivity: do special start mode where
+ * Flag for IActivityManager.startActivity: do special start mode where
* a new activity is launched only if it is needed.
* @hide
*/
public static final int START_FLAG_ONLY_IF_NEEDED = 1<<0;
/**
- * Flag for IActivityManaqer.startActivity: launch the app for
+ * Flag for IActivityManager.startActivity: launch the app for
* debugging.
* @hide
*/
public static final int START_FLAG_DEBUG = 1<<1;
/**
- * Flag for IActivityManaqer.startActivity: launch the app for
+ * Flag for IActivityManager.startActivity: launch the app for
* allocation tracking.
* @hide
*/
public static final int START_FLAG_TRACK_ALLOCATION = 1<<2;
/**
- * Flag for IActivityManaqer.startActivity: launch the app with
+ * Flag for IActivityManager.startActivity: launch the app with
* native debugging support.
* @hide
*/
public static final int START_FLAG_NATIVE_DEBUGGING = 1<<3;
/**
- * Flag for IActivityManaqer.startActivity: launch the app for
+ * Flag for IActivityManager.startActivity: launch the app for
* debugging and suspend threads.
* @hide
*/
public static final int START_FLAG_DEBUG_SUSPEND = 1 << 4;
/**
- * Result for IActivityManaqer.broadcastIntent: success!
+ * Result for IActivityManager.broadcastIntent: success!
* @hide
*/
public static final int BROADCAST_SUCCESS = 0;
/**
- * Result for IActivityManaqer.broadcastIntent: attempt to broadcast
+ * Result for IActivityManager.broadcastIntent: attempt to broadcast
* a sticky intent without appropriate permission.
* @hide
*/
@@ -656,20 +653,20 @@ public class ActivityManager {
public static final int BROADCAST_FAILED_USER_STOPPED = -2;
/**
- * Type for IActivityManaqer.getIntentSender: this PendingIntent type is unknown.
+ * Type for IActivityManager.getIntentSender: this PendingIntent type is unknown.
* @hide
*/
public static final int INTENT_SENDER_UNKNOWN = 0;
/**
- * Type for IActivityManaqer.getIntentSender: this PendingIntent is
+ * Type for IActivityManager.getIntentSender: this PendingIntent is
* for a sendBroadcast operation.
* @hide
*/
public static final int INTENT_SENDER_BROADCAST = 1;
/**
- * Type for IActivityManaqer.getIntentSender: this PendingIntent is
+ * Type for IActivityManager.getIntentSender: this PendingIntent is
* for a startActivity operation.
* @hide
*/
@@ -677,21 +674,21 @@ public class ActivityManager {
public static final int INTENT_SENDER_ACTIVITY = 2;
/**
- * Type for IActivityManaqer.getIntentSender: this PendingIntent is
+ * Type for IActivityManager.getIntentSender: this PendingIntent is
* for an activity result operation.
* @hide
*/
public static final int INTENT_SENDER_ACTIVITY_RESULT = 3;
/**
- * Type for IActivityManaqer.getIntentSender: this PendingIntent is
+ * Type for IActivityManager.getIntentSender: this PendingIntent is
* for a startService operation.
* @hide
*/
public static final int INTENT_SENDER_SERVICE = 4;
/**
- * Type for IActivityManaqer.getIntentSender: this PendingIntent is
+ * Type for IActivityManager.getIntentSender: this PendingIntent is
* for a startForegroundService operation.
* @hide
*/
@@ -4975,6 +4972,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 +5417,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 +5432,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/ActivityOptions.java b/core/java/android/app/ActivityOptions.java
index b8c20bd97264..cc3745675bc9 100644
--- a/core/java/android/app/ActivityOptions.java
+++ b/core/java/android/app/ActivityOptions.java
@@ -488,6 +488,8 @@ public class ActivityOptions extends ComponentOptions {
private static final String KEY_ALLOW_PASS_THROUGH_ON_TOUCH_OUTSIDE =
"android.activity.allowPassThroughOnTouchOutside";
+ private static final String KEY_FLEXIBLE_LAUNCH_SIZE = "android.activity.flexibleLaunchSize";
+
/**
* @see #setLaunchCookie
* @hide
@@ -588,6 +590,7 @@ public class ActivityOptions extends ComponentOptions {
@BackgroundActivityStartMode
private int mPendingIntentCreatorBackgroundActivityStartMode =
MODE_BACKGROUND_ACTIVITY_START_SYSTEM_DEFINED;
+ private boolean mFlexibleLaunchSize = false;
private boolean mDisableStartingWindow;
private boolean mAllowPassThroughOnTouchOutside;
@@ -1451,6 +1454,7 @@ public class ActivityOptions extends ComponentOptions {
mPendingIntentCreatorBackgroundActivityStartMode = opts.getInt(
KEY_PENDING_INTENT_CREATOR_BACKGROUND_ACTIVITY_START_MODE,
MODE_BACKGROUND_ACTIVITY_START_SYSTEM_DEFINED);
+ mFlexibleLaunchSize = opts.getBoolean(KEY_FLEXIBLE_LAUNCH_SIZE, /* defaultValue = */ false);
mDisableStartingWindow = opts.getBoolean(KEY_DISABLE_STARTING_WINDOW);
mAllowPassThroughOnTouchOutside = opts.getBoolean(KEY_ALLOW_PASS_THROUGH_ON_TOUCH_OUTSIDE);
mAnimationAbortListener = IRemoteCallback.Stub.asInterface(
@@ -2346,6 +2350,24 @@ public class ActivityOptions extends ComponentOptions {
}
/**
+ * Sets whether the size of the launch bounds is flexible, meaning it can be overridden to a
+ * different size during the launch params calculation.
+ * @hide
+ */
+ public ActivityOptions setFlexibleLaunchSize(boolean isFlexible) {
+ mFlexibleLaunchSize = isFlexible;
+ return this;
+ }
+
+ /**
+ * Gets whether the size of the launch bounds is flexible.
+ * @hide
+ */
+ public boolean getFlexibleLaunchSize() {
+ return mFlexibleLaunchSize;
+ }
+
+ /**
* Update the current values in this ActivityOptions from those supplied
* in <var>otherOptions</var>. Any values
* defined in <var>otherOptions</var> replace those in the base options.
@@ -2601,6 +2623,9 @@ public class ActivityOptions extends ComponentOptions {
b.putInt(KEY_PENDING_INTENT_CREATOR_BACKGROUND_ACTIVITY_START_MODE,
mPendingIntentCreatorBackgroundActivityStartMode);
}
+ if (mFlexibleLaunchSize) {
+ b.putBoolean(KEY_FLEXIBLE_LAUNCH_SIZE, mFlexibleLaunchSize);
+ }
if (mDisableStartingWindow) {
b.putBoolean(KEY_DISABLE_STARTING_WINDOW, mDisableStartingWindow);
}
diff --git a/core/java/android/app/ActivityThread.java b/core/java/android/app/ActivityThread.java
index c765298035e0..2c1df73cc6cc 100644
--- a/core/java/android/app/ActivityThread.java
+++ b/core/java/android/app/ActivityThread.java
@@ -1137,12 +1137,19 @@ public final class ActivityThread extends ClientTransactionHandler
CompatibilityInfo compatInfo, int resultCode, String data, Bundle extras,
boolean ordered, boolean assumeDelivered, int sendingUser, int processState,
int sendingUid, String sendingPackage) {
+ long debugStoreId = -1;
+ if (DEBUG_STORE_ENABLED) {
+ debugStoreId = DebugStore.recordScheduleReceiver();
+ }
updateProcessState(processState, false);
ReceiverData r = new ReceiverData(intent, resultCode, data, extras,
ordered, false, assumeDelivered, mAppThread.asBinder(), sendingUser,
sendingUid, sendingPackage);
r.info = info;
sendMessage(H.RECEIVER, r);
+ if (DEBUG_STORE_ENABLED) {
+ DebugStore.recordEventEnd(debugStoreId);
+ }
}
public final void scheduleReceiverList(List<ReceiverInfo> info) throws RemoteException {
@@ -1490,6 +1497,10 @@ public final class ActivityThread extends ClientTransactionHandler
boolean sticky, boolean assumeDelivered, int sendingUser, int processState,
int sendingUid, String sendingPackage)
throws RemoteException {
+ long debugStoreId = -1;
+ if (DEBUG_STORE_ENABLED) {
+ debugStoreId = DebugStore.recordScheduleRegisteredReceiver();
+ }
updateProcessState(processState, false);
// We can't modify IIntentReceiver due to UnsupportedAppUsage, so
@@ -1514,6 +1525,9 @@ public final class ActivityThread extends ClientTransactionHandler
receiver.performReceive(intent, resultCode, dataStr, extras, ordered, sticky,
sendingUser);
}
+ if (DEBUG_STORE_ENABLED) {
+ DebugStore.recordEventEnd(debugStoreId);
+ }
}
@Override
@@ -2505,8 +2519,15 @@ public final class ActivityThread extends ClientTransactionHandler
switch (msg.what) {
case BIND_APPLICATION:
Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "bindApplication");
+ if (DEBUG_STORE_ENABLED) {
+ debugStoreId =
+ DebugStore.recordHandleBindApplication();
+ }
AppBindData data = (AppBindData)msg.obj;
handleBindApplication(data);
+ if (DEBUG_STORE_ENABLED) {
+ DebugStore.recordEventEnd(debugStoreId);
+ }
Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
break;
case EXIT_APPLICATION:
@@ -2529,7 +2550,8 @@ public final class ActivityThread extends ClientTransactionHandler
ReceiverData receiverData = (ReceiverData) msg.obj;
if (DEBUG_STORE_ENABLED) {
debugStoreId =
- DebugStore.recordBroadcastHandleReceiver(receiverData.intent);
+ DebugStore.recordBroadcastReceive(
+ receiverData.intent, System.identityHashCode(receiverData));
}
try {
@@ -7340,16 +7362,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..ea4646aa9eb9 100644
--- a/core/java/android/app/AppCompatTaskInfo.java
+++ b/core/java/android/app/AppCompatTaskInfo.java
@@ -102,6 +102,8 @@ 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;
@Retention(RetentionPolicy.SOURCE)
@IntDef(flag = true, value = {
@@ -115,7 +117,8 @@ 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
})
public @interface TopActivityFlag {}
@@ -133,7 +136,8 @@ public class AppCompatTaskInfo implements Parcelable {
@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 +304,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() {
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/ContextImpl.java b/core/java/android/app/ContextImpl.java
index 1a6e9b07067f..7e5c0fbe1ee1 100644
--- a/core/java/android/app/ContextImpl.java
+++ b/core/java/android/app/ContextImpl.java
@@ -22,14 +22,12 @@ import static android.os.StrictMode.vmIncorrectContextUseEnabled;
import static android.permission.flags.Flags.shouldRegisterAttributionSource;
import static android.view.WindowManager.LayoutParams.WindowType;
-import android.Manifest;
import android.annotation.CallbackExecutor;
import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.SuppressLint;
import android.annotation.UiContext;
-import android.companion.virtual.VirtualDevice;
import android.companion.virtual.VirtualDeviceManager;
import android.compat.annotation.UnsupportedAppUsage;
import android.content.AttributionSource;
@@ -2367,47 +2365,11 @@ class ContextImpl extends Context {
Log.v(TAG, "Treating renounced permission " + permission + " as denied");
return PERMISSION_DENIED;
}
- int deviceId = resolveDeviceIdForPermissionCheck(permission);
+ int deviceId = PermissionManager.resolveDeviceIdForPermissionCheck(this, getDeviceId(),
+ permission);
return PermissionManager.checkPermission(permission, pid, uid, deviceId);
}
- private int resolveDeviceIdForPermissionCheck(String permission) {
- // When checking a device-aware permission on a remote device, if the permission is CAMERA
- // or RECORD_AUDIO we need to check remote device's corresponding capability. If the remote
- // device doesn't have capability fall back to checking permission on the default device.
- // Note: we only perform permission check redirection when the device id is not explicitly
- // set in the context.
- int deviceId = getDeviceId();
- if (deviceId != Context.DEVICE_ID_DEFAULT
- && !mIsExplicitDeviceId
- && PermissionManager.DEVICE_AWARE_PERMISSIONS.contains(permission)) {
- VirtualDeviceManager virtualDeviceManager =
- getSystemService(VirtualDeviceManager.class);
- if (virtualDeviceManager == null) {
- Slog.e(
- TAG,
- "VDM is not enabled when device id is not default. deviceId = "
- + deviceId);
- } else {
- VirtualDevice virtualDevice = virtualDeviceManager.getVirtualDevice(deviceId);
- if (virtualDevice != null) {
- if ((Objects.equals(permission, Manifest.permission.RECORD_AUDIO)
- && !virtualDevice.hasCustomAudioInputSupport())
- || (Objects.equals(permission, Manifest.permission.CAMERA)
- && !virtualDevice.hasCustomCameraSupport())) {
- deviceId = Context.DEVICE_ID_DEFAULT;
- }
- } else {
- Slog.e(
- TAG,
- "virtualDevice is not found when device id is not default. deviceId = "
- + deviceId);
- }
- }
- }
- return deviceId;
- }
-
/** @hide */
@Override
public int checkPermission(String permission, int pid, int uid, IBinder callerToken) {
@@ -2511,7 +2473,8 @@ class ContextImpl extends Context {
@Override
public int getPermissionRequestState(String permission) {
Objects.requireNonNull(permission, "Permission name can't be null");
- int deviceId = resolveDeviceIdForPermissionCheck(permission);
+ int deviceId = PermissionManager.resolveDeviceIdForPermissionCheck(this, getDeviceId(),
+ permission);
PermissionManager permissionManager = getSystemService(PermissionManager.class);
return permissionManager.getPermissionRequestState(getOpPackageName(), permission,
deviceId);
@@ -2981,7 +2944,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/DreamManager.java b/core/java/android/app/DreamManager.java
index c597a9dcae7e..555006b97911 100644
--- a/core/java/android/app/DreamManager.java
+++ b/core/java/android/app/DreamManager.java
@@ -71,8 +71,11 @@ public class DreamManager {
@TestApi
@RequiresPermission(WRITE_SECURE_SETTINGS)
public void setScreensaverEnabled(boolean enabled) {
- Settings.Secure.putIntForUser(mContext.getContentResolver(),
- Settings.Secure.SCREENSAVER_ENABLED, enabled ? 1 : 0, UserHandle.USER_CURRENT);
+ try {
+ mService.setScreensaverEnabled(enabled);
+ } catch (RemoteException e) {
+ e.rethrowFromSystemServer();
+ }
}
/**
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/LoadedApk.java b/core/java/android/app/LoadedApk.java
index ffd235f91e09..d0949ad86e04 100644
--- a/core/java/android/app/LoadedApk.java
+++ b/core/java/android/app/LoadedApk.java
@@ -61,6 +61,7 @@ import android.view.DisplayAdjustments;
import com.android.internal.R;
import com.android.internal.annotations.GuardedBy;
+import com.android.internal.os.DebugStore;
import com.android.internal.util.ArrayUtils;
import dalvik.system.BaseDexClassLoader;
@@ -107,6 +108,9 @@ public final class LoadedApk {
static final String TAG = "LoadedApk";
static final boolean DEBUG = false;
+ private static final boolean DEBUG_STORE_ENABLED =
+ com.android.internal.os.Flags.debugStoreEnabled();
+
@UnsupportedAppUsage
private final ActivityThread mActivityThread;
@UnsupportedAppUsage
@@ -1816,6 +1820,12 @@ public final class LoadedApk {
Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER,
"broadcastReceiveReg: " + intent.getAction());
}
+ long debugStoreId = -1;
+ if (DEBUG_STORE_ENABLED) {
+ debugStoreId =
+ DebugStore.recordBroadcastReceiveReg(
+ intent, System.identityHashCode(this));
+ }
try {
ClassLoader cl = mReceiver.getClass().getClassLoader();
@@ -1838,6 +1848,10 @@ public final class LoadedApk {
"Error receiving broadcast " + intent
+ " in " + mReceiver, e);
}
+ } finally {
+ if (DEBUG_STORE_ENABLED) {
+ DebugStore.recordEventEnd(debugStoreId);
+ }
}
if (receiver.getPendingResult() != null) {
diff --git a/core/java/android/app/Notification.java b/core/java/android/app/Notification.java
index 1b71e73db852..127a08b04e87 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);
}
/**
@@ -9526,14 +9549,21 @@ public class Notification implements Parcelable
.viewType(viewType)
.highlightExpander(isConversationLayout)
.hideProgress(true)
- .text(null)
.hideLeftIcon(isOneToOne)
.hideRightIcon(hideRightIcons || isOneToOne);
if (notificationsRedesignTemplates()) {
+ String lastMessage = !mMessages.isEmpty()
+ ? mMessages.getLast().mText.toString() : null;
+
p.title(conversationTitle)
+ // The text is not actually displayed like this (since we're using a
+ // MessagingLinearLayout instead of the regular text), but we're using it to
+ // know whether the notification will have a second line in practice.
+ .text(lastMessage)
.hideAppName(isCollapsed);
} else {
p.title(isLegacyHeaderless ? conversationTitle : null)
+ .text(null)
.headerTextSecondary(isLegacyHeaderless ? null : conversationTitle);
}
RemoteViews contentView = mBuilder.applyStandardTemplateWithActions(
@@ -14652,13 +14682,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/PropertyInvalidatedCache.java b/core/java/android/app/PropertyInvalidatedCache.java
index c573161f30cc..6e495768bfd4 100644
--- a/core/java/android/app/PropertyInvalidatedCache.java
+++ b/core/java/android/app/PropertyInvalidatedCache.java
@@ -293,6 +293,7 @@ public class PropertyInvalidatedCache<Query, Result> {
// The test mode. This is only used to ensure that the test functions setTestMode() and
// testPropertyName() are used correctly.
+ @GuardedBy("sGlobalLock")
private static boolean sTestMode = false;
/**
@@ -668,7 +669,7 @@ public class PropertyInvalidatedCache<Query, Result> {
// True if this handler is in test mode. If it is in test mode, then nonces are stored
// and retrieved from mTestNonce.
@GuardedBy("mLock")
- private boolean mTestMode = false;
+ private boolean mTestMode;
// This is the local value of the nonce, as last set by the NonceHandler. It is always
// updated by the setNonce() operation. The getNonce() operation returns this value in
@@ -692,6 +693,9 @@ public class PropertyInvalidatedCache<Query, Result> {
NonceHandler(@NonNull String name) {
mName = name;
+ synchronized (sGlobalLock) {
+ mTestMode = sTestMode;
+ }
}
/**
@@ -1413,14 +1417,49 @@ public class PropertyInvalidatedCache<Query, Result> {
}
/**
- * Enable or disable testing. The protocol requires that the mode toggle: for instance, it is
- * illegal to clear the test mode if the test mode is already off. The purpose is solely to
- * ensure that test clients do not forget to use the test mode properly, even though the
- * current logic does not care.
+ * Throw if the current process is not allowed to use test APIs.
+ */
+ @android.ravenwood.annotation.RavenwoodReplace
+ private static void throwIfNotTest() {
+ final ActivityThread activityThread = ActivityThread.currentActivityThread();
+ if (activityThread == null) {
+ // Only tests can reach here.
+ return;
+ }
+ final Instrumentation instrumentation = activityThread.getInstrumentation();
+ if (instrumentation == null) {
+ // Only tests can reach here.
+ return;
+ }
+ if (instrumentation.isInstrumenting()) {
+ return;
+ }
+ if (Flags.enforcePicTestmodeProtocol()) {
+ throw new IllegalStateException("Test-only API called not from a test.");
+ }
+ }
+
+ /**
+ * Do not throw if running under ravenwood.
+ */
+ private static void throwIfNotTest$ravenwood() {
+ }
+
+ /**
+ * Enable or disable test mode. The protocol requires that the mode toggle: for instance, it is
+ * illegal to clear the test mode if the test mode is already off. Enabling test mode puts
+ * all caches in the process into test mode; all nonces are initialized to UNSET and
+ * subsequent reads and writes are to process memory. This has the effect of disabling all
+ * caches that are not local to the process. Disabling test mode restores caches to normal
+ * operation.
+ * @param mode The desired test mode.
+ * @throws IllegalStateException if the supplied mode is already set.
+ * @throws IllegalStateException if the process is not running an instrumentation test.
* @hide
*/
@VisibleForTesting
public static void setTestMode(boolean mode) {
+ throwIfNotTest();
synchronized (sGlobalLock) {
if (sTestMode == mode) {
final String msg = "cannot set test mode redundantly: mode=" + mode;
@@ -1431,10 +1470,8 @@ public class PropertyInvalidatedCache<Query, Result> {
}
}
sTestMode = mode;
- if (mode) {
- // No action when testing begins.
- } else {
- resetAfterTestLocked();
+ if (Flags.picTestMode() || !mode) {
+ setTestModeLocked(mode);
}
}
}
@@ -1445,11 +1482,11 @@ public class PropertyInvalidatedCache<Query, Result> {
* that were not originally in test mode.
*/
@GuardedBy("sGlobalLock")
- private static void resetAfterTestLocked() {
+ private static void setTestModeLocked(boolean mode) {
for (Iterator<String> e = sHandlers.keys().asIterator(); e.hasNext(); ) {
String s = e.next();
final NonceHandler h = sHandlers.get(s);
- h.setTestMode(false);
+ h.setTestMode(mode);
}
}
@@ -1458,9 +1495,11 @@ public class PropertyInvalidatedCache<Query, Result> {
* for which it would not otherwise have permission. Caches in test mode do NOT write their
* values to the system properties. The effect is local to the current process. Test mode
* must be true when this method is called.
+ * @throws IllegalStateException if the process is not running an instrumentation test.
* @hide
*/
public void testPropertyName() {
+ throwIfNotTest();
synchronized (sGlobalLock) {
if (sTestMode == false) {
throw new IllegalStateException("cannot test property name with test mode off");
@@ -1771,10 +1810,12 @@ public class PropertyInvalidatedCache<Query, Result> {
* When multiple caches share a single property value, using an instance method on one of
* the cache objects to invalidate all of the cache objects becomes confusing and you should
* just use the static version of this function.
+ * @throws IllegalStateException if the process is not running an instrumentation test.
* @hide
*/
@VisibleForTesting
public void disableSystemWide() {
+ throwIfNotTest();
disableSystemWide(mPropertyName);
}
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..73de1b67dc66 100644
--- a/core/java/android/app/admin/DevicePolicyManager.java
+++ b/core/java/android/app/admin/DevicePolicyManager.java
@@ -8269,6 +8269,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/PolicyUpdateReceiver.java b/core/java/android/app/admin/PolicyUpdateReceiver.java
index be13988d7c76..630ab0ece07d 100644
--- a/core/java/android/app/admin/PolicyUpdateReceiver.java
+++ b/core/java/android/app/admin/PolicyUpdateReceiver.java
@@ -20,10 +20,12 @@ import android.annotation.BroadcastBehavior;
import android.annotation.NonNull;
import android.annotation.SdkConstant;
import android.annotation.TestApi;
+import android.app.admin.flags.Flags;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.os.Bundle;
+import android.text.TextUtils;
import android.util.Log;
import java.util.Objects;
@@ -46,6 +48,10 @@ import java.util.Objects;
public abstract class PolicyUpdateReceiver extends BroadcastReceiver {
private static String TAG = "PolicyUpdateReceiver";
+ //TODO(b/378931989): Switch to android.app.admin.DevicePolicyIdentifiers.MEMORY_TAGGING_POLICY
+ //when the appropriate flag is launched.
+ private static final String MEMORY_TAGGING_POLICY = "memoryTagging";
+
/**
* Action for a broadcast sent to admins to communicate back the result of setting a policy in
* {@link DevicePolicyManager}.
@@ -156,15 +162,28 @@ public abstract class PolicyUpdateReceiver extends BroadcastReceiver {
@Override
public final void onReceive(Context context, Intent intent) {
Objects.requireNonNull(intent.getAction());
+ String policyKey;
switch (intent.getAction()) {
case ACTION_DEVICE_POLICY_SET_RESULT:
Log.i(TAG, "Received ACTION_DEVICE_POLICY_SET_RESULT");
- onPolicySetResult(context, getPolicyKey(intent), getPolicyExtraBundle(intent),
+ policyKey = getPolicyKey(intent);
+ if (!shouldPropagatePolicy(policyKey)) {
+ Log.d(TAG, TextUtils.formatSimple(
+ "Skipping propagation of policy %s", policyKey));
+ break;
+ }
+ onPolicySetResult(context, policyKey, getPolicyExtraBundle(intent),
getTargetUser(intent), getPolicyChangedReason(intent));
break;
case ACTION_DEVICE_POLICY_CHANGED:
Log.i(TAG, "Received ACTION_DEVICE_POLICY_CHANGED");
- onPolicyChanged(context, getPolicyKey(intent), getPolicyExtraBundle(intent),
+ policyKey = getPolicyKey(intent);
+ if (!shouldPropagatePolicy(policyKey)) {
+ Log.d(TAG, TextUtils.formatSimple(
+ "Skipping propagation of policy %s", policyKey));
+ break;
+ }
+ onPolicyChanged(context, policyKey, getPolicyExtraBundle(intent),
getTargetUser(intent), getPolicyChangedReason(intent));
break;
default:
@@ -217,6 +236,14 @@ public abstract class PolicyUpdateReceiver extends BroadcastReceiver {
return new TargetUser(targetUserId);
}
+ /**
+ * @hide
+ */
+ private boolean shouldPropagatePolicy(String policyKey) {
+ return !MEMORY_TAGGING_POLICY.equals(policyKey) || Flags.setMtePolicyCoexistence();
+ }
+
+
// TODO(b/260847505): Add javadocs to explain which DPM APIs are supported
/**
* Callback triggered after an admin has set a policy using one of the APIs in
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/backup/BackupManagerMonitor.java b/core/java/android/app/backup/BackupManagerMonitor.java
index e741bc2bf608..19c24cd8a14a 100644
--- a/core/java/android/app/backup/BackupManagerMonitor.java
+++ b/core/java/android/app/backup/BackupManagerMonitor.java
@@ -297,6 +297,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/contextualsearch/flags.aconfig b/core/java/android/app/contextualsearch/flags.aconfig
index 1de034b23a7a..1a14b20d6c09 100644
--- a/core/java/android/app/contextualsearch/flags.aconfig
+++ b/core/java/android/app/contextualsearch/flags.aconfig
@@ -23,6 +23,18 @@ flag {
bug: "371065456"
}
+
+flag {
+ name: "report_secure_surfaces_in_assist_structure"
+ namespace: "windowing_frontend"
+ description: "SurfaceView reports when the surface is using a SECURE flag."
+ bug: "390504528"
+ metadata {
+ purpose: PURPOSE_BUGFIX
+ }
+}
+
+
flag {
name: "contextual_search_prevent_self_capture"
namespace: "sysui_integrations"
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/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..5c267c9f6475 100644
--- a/core/java/android/app/notification.aconfig
+++ b/core/java/android/app/notification.aconfig
@@ -255,6 +255,16 @@ 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: "api_rich_ongoing"
is_exported: true
namespace: "systemui"
diff --git a/core/java/android/app/performance.aconfig b/core/java/android/app/performance.aconfig
index 2569f7b99dca..82875ebb0f77 100644
--- a/core/java/android/app/performance.aconfig
+++ b/core/java/android/app/performance.aconfig
@@ -50,3 +50,10 @@ flag {
description: "Cache null returns from binder calls"
bug: "372923336"
}
+
+flag {
+ namespace: "system_performance"
+ name: "pic_test_mode"
+ description: "Updated test mode for PIC"
+ bug: "396173886"
+}
diff --git a/core/java/android/app/servertransaction/ClientTransaction.java b/core/java/android/app/servertransaction/ClientTransaction.java
index 6e3e86cfbffc..e726bc9df4d5 100644
--- a/core/java/android/app/servertransaction/ClientTransaction.java
+++ b/core/java/android/app/servertransaction/ClientTransaction.java
@@ -236,9 +236,17 @@ public class ClientTransaction implements Parcelable {
* 2. The transaction message is scheduled.
* 3. The client calls {@link TransactionExecutor#execute(ClientTransaction)}, which executes
* all callbacks and necessary lifecycle transitions.
+ *
+ * @return {@link RemoteException} if the transaction failed.
*/
- public void schedule() throws RemoteException {
- mClient.scheduleTransaction(this);
+ @Nullable
+ public RemoteException schedule() {
+ try {
+ mClient.scheduleTransaction(this);
+ return null;
+ } catch (RemoteException e) {
+ return e;
+ }
}
// Parcelable implementation
diff --git a/core/java/android/app/supervision/SupervisionManager.java b/core/java/android/app/supervision/SupervisionManager.java
index 0270edf080a9..8217ca1953ab 100644
--- a/core/java/android/app/supervision/SupervisionManager.java
+++ b/core/java/android/app/supervision/SupervisionManager.java
@@ -97,7 +97,7 @@ public class SupervisionManager {
*
* @hide
*/
- @RequiresPermission(value = android.Manifest.permission.QUERY_USERS)
+ @RequiresPermission(anyOf = {MANAGE_USERS, QUERY_USERS})
@Nullable
public Intent createConfirmSupervisionCredentialsIntent() {
if (mService != null) {
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/BroadcastReceiver.java b/core/java/android/content/BroadcastReceiver.java
index cf684876d33f..357baa3c1099 100644
--- a/core/java/android/content/BroadcastReceiver.java
+++ b/core/java/android/content/BroadcastReceiver.java
@@ -263,7 +263,7 @@ public abstract class BroadcastReceiver {
1);
}
if (DEBUG_STORE_ENABLED) {
- DebugStore.recordFinish(mReceiverClassName);
+ DebugStore.recordFinish(System.identityHashCode(this));
}
if (mType == TYPE_COMPONENT) {
@@ -444,7 +444,7 @@ public abstract class BroadcastReceiver {
PendingResult res = mPendingResult;
mPendingResult = null;
if (DEBUG_STORE_ENABLED) {
- DebugStore.recordGoAsync(getClass().getName());
+ DebugStore.recordGoAsync(System.identityHashCode(res));
}
if (res != null && Trace.isTagEnabled(Trace.TRACE_TAG_ACTIVITY_MANAGER)) {
res.mReceiverClassName = getClass().getName();
diff --git a/core/java/android/content/pm/ActivityInfo.java b/core/java/android/content/pm/ActivityInfo.java
index fd59ea9d18fb..f61d695bbfc2 100644
--- a/core/java/android/content/pm/ActivityInfo.java
+++ b/core/java/android/content/pm/ActivityInfo.java
@@ -950,6 +950,8 @@ public class ActivityInfo extends ComponentInfo implements Parcelable {
CONFIG_COLOR_MODE,
CONFIG_FONT_SCALE,
CONFIG_GRAMMATICAL_GENDER,
+ CONFIG_FONT_WEIGHT_ADJUSTMENT,
+ CONFIG_WINDOW_CONFIGURATION,
CONFIG_ASSETS_PATHS,
CONFIG_RESOURCES_UNUSED,
})
diff --git a/core/java/android/content/pm/RegisteredServicesCache.java b/core/java/android/content/pm/RegisteredServicesCache.java
index 74da62c85ed2..10d3051cff6f 100644
--- a/core/java/android/content/pm/RegisteredServicesCache.java
+++ b/core/java/android/content/pm/RegisteredServicesCache.java
@@ -166,7 +166,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 +192,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);
@@ -918,4 +926,25 @@ public abstract class RegisteredServicesCache<V> {
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/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/flags.aconfig b/core/java/android/content/pm/flags.aconfig
index 255a08cf170f..42bef0e91539 100644
--- a/core/java/android/content/pm/flags.aconfig
+++ b/core/java/android/content/pm/flags.aconfig
@@ -400,3 +400,11 @@ flag {
bug: "378539511"
is_fixed_read_only: true
}
+
+flag {
+ name: "cloud_compilation_verification"
+ namespace: "art_mainline"
+ description: "Feature flag to enable the Cloud Compilation install-time verification in the package manager."
+ bug: "377474232"
+ is_fixed_read_only: true
+}
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/content/pm/parsing/OWNERS b/core/java/android/content/pm/parsing/OWNERS
index 445a8330037b..b8fa1a93ed78 100644
--- a/core/java/android/content/pm/parsing/OWNERS
+++ b/core/java/android/content/pm/parsing/OWNERS
@@ -1,4 +1,3 @@
# Bug component: 36137
-chiuwinson@google.com
patb@google.com
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/gesture/OWNERS b/core/java/android/gesture/OWNERS
index 168630af6da4..ffa753aa65b2 100644
--- a/core/java/android/gesture/OWNERS
+++ b/core/java/android/gesture/OWNERS
@@ -3,5 +3,4 @@
romainguy@google.com
adamp@google.com
aurimas@google.com
-nduca@google.com
sumir@google.com
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/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/usb/IUsbManagerInternal.aidl b/core/java/android/hardware/usb/IUsbManagerInternal.aidl
new file mode 100644
index 000000000000..32479d449f24
--- /dev/null
+++ b/core/java/android/hardware/usb/IUsbManagerInternal.aidl
@@ -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 android.hardware.usb;
+
+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/metrics/OWNERS b/core/java/android/metrics/OWNERS
index ba867e0cad2b..98aaf3f47b21 100644
--- a/core/java/android/metrics/OWNERS
+++ b/core/java/android/metrics/OWNERS
@@ -1,4 +1,3 @@
# Bug component: 26805
cwren@android.com
-cwren@google.com
diff --git a/core/java/android/os/Build.java b/core/java/android/os/Build.java
index 1e6469c57fa9..0ceafa01017e 100644
--- a/core/java/android/os/Build.java
+++ b/core/java/android/os/Build.java
@@ -1515,7 +1515,7 @@ public class Build {
VERSION_CODES.VANILLA_ICE_CREAM * SDK_INT_MULTIPLIER;
/**
- * The upcoming, not yet finalized, version of Android.
+ * Android 36.0.
*/
public static final int BAKLAVA = VERSION_CODES.BAKLAVA * SDK_INT_MULTIPLIER;
}
diff --git a/core/java/android/os/CombinedMessageQueue/MessageQueue.java b/core/java/android/os/CombinedMessageQueue/MessageQueue.java
index 74972346bf2e..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>();
@@ -92,11 +97,8 @@ public final class MessageQueue {
// queue for async messages when inserting a message at the tail.
private int mAsyncMessageCount;
- /**
- * @hide
- */
private final AtomicLong mMessageCount = new AtomicLong();
- private final Thread mThread;
+ private final String mThreadName;
private final long mTid;
/**
@@ -133,7 +135,7 @@ public final class MessageQueue {
mUseConcurrent = sIsProcessAllowedToUseConcurrent;
mQuitAllowed = quitAllowed;
mPtr = nativeInit();
- mThread = Thread.currentThread();
+ mThreadName = Thread.currentThread().getName();
mTid = Process.myTid();
}
@@ -142,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();
@@ -223,10 +234,10 @@ public final class MessageQueue {
traceMessageCount();
PerfettoTrace.instant(PerfettoTrace.MQ_CATEGORY, "message_queue_send")
- .addFlow(msg.mEventId.get())
+ .setFlow(msg.mEventId.get())
.beginProto()
.beginNested(2004 /* message_queue */)
- .addField(2 /* receiving_thread_name */, mThread.getName())
+ .addField(2 /* receiving_thread_name */, mThreadName)
.addField(3 /* message_code */, msg.what)
.addField(4 /* message_delay_ms */, when - SystemClock.uptimeMillis())
.endNested()
@@ -237,7 +248,7 @@ public final class MessageQueue {
/** @hide */
private void traceMessageCount() {
PerfettoTrace.counter(PerfettoTrace.MQ_CATEGORY, mMessageCount.get())
- .usingThreadCounterTrack(mTid, mThread.getName())
+ .usingThreadCounterTrack(mTid, mThreadName)
.emit();
}
@@ -998,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/IBinder.java b/core/java/android/os/IBinder.java
index 8cfd32449537..48e6249d004a 100644
--- a/core/java/android/os/IBinder.java
+++ b/core/java/android/os/IBinder.java
@@ -451,7 +451,7 @@ public interface IBinder {
* @param executor The executor on which to run the callback.
* @param callback The callback used to deliver state change notifications.
*
- * <p>@throws {@link UnsupportedOperationException} if the kernel binder driver does not support
+ * @throws {@link UnsupportedOperationException} if the kernel binder driver does not support
* this feature.
*/
@FlaggedApi(Flags.FLAG_BINDER_FROZEN_STATE_CHANGE_CALLBACK)
diff --git a/core/java/android/os/IpcDataCache.java b/core/java/android/os/IpcDataCache.java
index 240bc4f74820..e888f520b842 100644
--- a/core/java/android/os/IpcDataCache.java
+++ b/core/java/android/os/IpcDataCache.java
@@ -718,12 +718,19 @@ public class IpcDataCache<Query, Result> extends PropertyInvalidatedCache<Query,
}
/**
- * Enable or disable testing. The protocol requires that the mode toggle: for instance, it is
- * illegal to clear the test mode if the test mode is already off. The purpose is solely to
- * ensure that test clients do not forget to use the test mode properly, even though the
- * current logic does not care.
+ * Enable or disable test mode. The protocol requires that the mode toggle: for instance, it is
+ * illegal to clear the test mode if the test mode is already off. Enabling test mode puts
+ * all caches in the process into test mode; all nonces are initialized to UNSET and
+ * subsequent reads and writes are to process memory. This has the effect of disabling all
+ * caches that are not local to the process. Disabling test mode restores caches to normal
+ * operation.
+ * @param mode The desired test mode.
+ * @throws IllegalStateException if the supplied mode is already set.
+ * @throws IllegalStateException if the process is not running an instrumentation test.
* @hide
*/
+ @FlaggedApi(android.os.Flags.FLAG_IPC_DATA_CACHE_TEST_APIS)
+ @SystemApi(client=SystemApi.Client.MODULE_LIBRARIES)
@TestApi
public static void setTestMode(boolean mode) {
PropertyInvalidatedCache.setTestMode(mode);
diff --git a/core/java/android/os/Looper.java b/core/java/android/os/Looper.java
index 1329b90538bb..3ff6e052335e 100644
--- a/core/java/android/os/Looper.java
+++ b/core/java/android/os/Looper.java
@@ -205,7 +205,7 @@ public final class Looper {
.addField(1 /* sending_thread_name */, msg.mSendingThreadName)
.endNested()
.endProto()
- .addTerminatingFlow(msg.mEventId.get())
+ .setTerminatingFlow(msg.mEventId.get())
.emit();
// This must be in a local variabe, in case a UI event sets the logger
diff --git a/core/java/android/os/Message.java b/core/java/android/os/Message.java
index b22d1774d967..69e84e3b097a 100644
--- a/core/java/android/os/Message.java
+++ b/core/java/android/os/Message.java
@@ -23,7 +23,7 @@ import android.util.proto.ProtoOutputStream;
import com.android.internal.annotations.VisibleForTesting;
-import java.util.concurrent.atomic.AtomicInteger;
+import java.util.concurrent.atomic.AtomicLong;
/**
*
@@ -43,7 +43,7 @@ public final class Message implements Parcelable {
*
* @hide Only for use within the system server.
*/
- public final AtomicInteger mEventId = new AtomicInteger();
+ public final AtomicLong mEventId = new AtomicLong();
/**
* User-defined message code so that the recipient can identify
diff --git a/core/java/android/os/PerfettoTrackEventExtra.java b/core/java/android/os/PerfettoTrackEventExtra.java
index f4b5dfe76f88..07b44a87ef88 100644
--- a/core/java/android/os/PerfettoTrackEventExtra.java
+++ b/core/java/android/os/PerfettoTrackEventExtra.java
@@ -172,7 +172,6 @@ public final class PerfettoTrackEventExtra {
private final Pool<FieldDouble> mFieldDoubleCache;
private final Pool<FieldString> mFieldStringCache;
private final Pool<FieldNested> mFieldNestedCache;
- private final Pool<Flow> mFlowCache;
private final Pool<Builder> mBuilderCache;
private Builder() {
@@ -187,7 +186,6 @@ public final class PerfettoTrackEventExtra {
mFieldDoubleCache = mExtra.mFieldDoubleCache;
mFieldStringCache = mExtra.mFieldStringCache;
mFieldNestedCache = mExtra.mFieldNestedCache;
- mFlowCache = mExtra.mFlowCache;
mBuilderCache = mExtra.mBuilderCache;
mCounterInt64 = mExtra.getCounterInt64();
@@ -216,9 +214,6 @@ public final class PerfettoTrackEventExtra {
* Initialize the builder for a new trace event.
*/
public Builder init(int traceType, PerfettoTrace.Category category) {
- if (!category.isEnabled()) {
- return this;
- }
mTraceType = traceType;
mCategory = category;
mEventName = "";
@@ -227,11 +222,10 @@ public final class PerfettoTrackEventExtra {
mFieldStringCache.reset();
mFieldNestedCache.reset();
mBuilderCache.reset();
- mFlowCache.reset();
mExtra.reset();
// Reset after on init in case the thread created builders without calling emit
- return initInternal(this, null, true);
+ return initInternal(this, null, category.isEnabled());
}
/**
@@ -325,39 +319,7 @@ public final class PerfettoTrackEventExtra {
/**
* Adds a flow with {@code id}.
*/
- public Builder addFlow(int id) {
- if (!mIsCategoryEnabled) {
- return this;
- }
- if (DEBUG) {
- checkParent();
- }
- Flow flow = mFlowCache.get(sFlowSupplier);
- flow.setProcessFlow(id);
- mExtra.addPerfettoPointer(flow);
- return this;
- }
-
- /**
- * Adds a terminating flow with {@code id}.
- */
- public Builder addTerminatingFlow(int id) {
- if (!mIsCategoryEnabled) {
- return this;
- }
- if (DEBUG) {
- checkParent();
- }
- Flow flow = mFlowCache.get(sFlowSupplier);
- flow.setProcessTerminatingFlow(id);
- mExtra.addPerfettoPointer(flow);
- return this;
- }
-
- /**
- * Adds a flow with {@code id}.
- */
- public Builder setFlow(int id) {
+ public Builder setFlow(long id) {
if (!mIsCategoryEnabled) {
return this;
}
@@ -372,7 +334,7 @@ public final class PerfettoTrackEventExtra {
/**
* Adds a terminating flow with {@code id}.
*/
- public Builder setTerminatingFlow(int id) {
+ public Builder setTerminatingFlow(long id) {
if (!mIsCategoryEnabled) {
return this;
}
@@ -670,7 +632,6 @@ public final class PerfettoTrackEventExtra {
private final Pool<FieldDouble> mFieldDoubleCache = new Pool(DEFAULT_EXTRA_CACHE_SIZE);
private final Pool<FieldString> mFieldStringCache = new Pool(DEFAULT_EXTRA_CACHE_SIZE);
private final Pool<FieldNested> mFieldNestedCache = new Pool(DEFAULT_EXTRA_CACHE_SIZE);
- private final Pool<Flow> mFlowCache = new Pool(DEFAULT_EXTRA_CACHE_SIZE);
private final Pool<Builder> mBuilderCache = new Pool(DEFAULT_EXTRA_CACHE_SIZE);
private static final NativeAllocationRegistry sRegistry =
@@ -713,6 +674,7 @@ public final class PerfettoTrackEventExtra {
/**
* Resets the track event extra.
*/
+ @android.ravenwood.annotation.RavenwoodReplace
public void reset() {
native_clear_args(mPtr);
mPendingPointers.clear();
@@ -1342,4 +1304,8 @@ public final class PerfettoTrackEventExtra {
// Tracing currently completely disabled under Ravenwood
return null;
}
+
+ private void reset$ravenwood() {
+ // Tracing currently completely disabled under Ravenwood
+ }
}
diff --git a/core/java/android/os/PowerManagerInternal.java b/core/java/android/os/PowerManagerInternal.java
index 77b6d70339ab..2736b60cd1f6 100644
--- a/core/java/android/os/PowerManagerInternal.java
+++ b/core/java/android/os/PowerManagerInternal.java
@@ -357,4 +357,9 @@ public abstract class PowerManagerInternal {
* This may affect dream eligibility.
*/
public abstract void setDevicePostured(boolean isPostured);
+
+ /**
+ * Notifies PowerManager that settings have changed and that it should refresh its state.
+ */
+ public abstract void updateSettings();
}
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/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..5d80119410e1 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"
@@ -219,6 +227,14 @@ flag {
}
flag {
+ name: "ipc_data_cache_test_apis"
+ namespace: "system_performance"
+ description: "Expose IpcDataCache test apis to mainline modules."
+ bug: "396173886"
+ is_exported: true
+}
+
+flag {
name: "mainline_vcn_platform_api"
namespace: "vcn"
description: "Expose platform APIs to mainline VCN"
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/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 518820430419..561a2c96e6a7 100644
--- a/core/java/android/permission/PermissionManager.java
+++ b/core/java/android/permission/PermissionManager.java
@@ -16,6 +16,7 @@
package android.permission;
+import static android.companion.virtual.VirtualDeviceManager.PERSISTENT_DEVICE_ID_DEFAULT;
import static android.content.pm.PackageManager.FLAG_PERMISSION_GRANTED_BY_DEFAULT;
import static android.content.pm.PackageManager.FLAG_PERMISSION_GRANTED_BY_ROLE;
import static android.content.pm.PackageManager.FLAG_PERMISSION_POLICY_FIXED;
@@ -2039,12 +2040,49 @@ public final class PermissionManager {
new PackageNamePermissionQuery(permName, pkgName, persistentDeviceId, userId));
}
+ /**
+ * When checking a device-aware permission on a remote device, if the permission is CAMERA
+ * or RECORD_AUDIO we need to check remote device's corresponding capability. If the remote
+ * device doesn't have capability fall back to checking permission on the default device.
+ *
+ * @hide
+ */
+ public static int resolveDeviceIdForPermissionCheck(@NonNull Context context, int deviceId,
+ @Nullable String permission) {
+ if (deviceId == Context.DEVICE_ID_DEFAULT || !DEVICE_AWARE_PERMISSIONS.contains(
+ permission)) {
+ return Context.DEVICE_ID_DEFAULT;
+ }
+
+ VirtualDeviceManager virtualDeviceManager =
+ context.getSystemService(VirtualDeviceManager.class);
+ if (virtualDeviceManager == null) {
+ Slog.e(LOG_TAG, "VDM is not enabled when device id is not default. deviceId = "
+ + deviceId);
+ } else {
+ VirtualDevice virtualDevice = virtualDeviceManager.getVirtualDevice(deviceId);
+ if (virtualDevice != null) {
+ if ((Objects.equals(permission, Manifest.permission.RECORD_AUDIO)
+ && !virtualDevice.hasCustomAudioInputSupport())
+ || (Objects.equals(permission, Manifest.permission.CAMERA)
+ && !virtualDevice.hasCustomCameraSupport())) {
+ deviceId = Context.DEVICE_ID_DEFAULT;
+ }
+ } else {
+ Slog.e(LOG_TAG,
+ "virtualDevice is not found when device id is not default. deviceId = "
+ + deviceId);
+ }
+ }
+ return deviceId;
+ }
+
@Nullable
private String getPersistentDeviceId(int deviceId) {
String persistentDeviceId = null;
if (deviceId == Context.DEVICE_ID_DEFAULT) {
- persistentDeviceId = VirtualDeviceManager.PERSISTENT_DEVICE_ID_DEFAULT;
+ persistentDeviceId = PERSISTENT_DEVICE_ID_DEFAULT;
} else {
VirtualDeviceManager virtualDeviceManager = mContext.getSystemService(
VirtualDeviceManager.class);
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/PreferenceActivity.java b/core/java/android/preference/PreferenceActivity.java
index 25e8a4ddffcd..3fd94180c433 100644
--- a/core/java/android/preference/PreferenceActivity.java
+++ b/core/java/android/preference/PreferenceActivity.java
@@ -749,6 +749,10 @@ public abstract class PreferenceActivity extends ListActivity implements
getListView().clearChoices();
} else if (!WindowOnBackInvokedDispatcher.isOnBackInvokedCallbackEnabled(this)) {
super.onBackPressed();
+ } else if (!mIsBackCallbackRegistered) {
+ // If predictive back is enabled and no callback is registered, finish the activity.
+ // This ensures correct back navigation behaviour when onBackPressed is called manually.
+ finish();
}
updateBackCallbackRegistrationState();
}
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 89f66c010976..da4709b4b8b1 100644
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -8651,6 +8651,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
@@ -9357,6 +9385,16 @@ public final class Settings {
"accessibility_autoclick_panel_position";
/**
+ * Setting that specifies whether autoclick type reverts to left click after performing
+ * an action when {@link #ACCESSIBILITY_AUTOCLICK_ENABLED} is set.
+ *
+ * @see #ACCESSIBILITY_AUTOCLICK_ENABLED
+ * @hide
+ */
+ public static final String ACCESSIBILITY_AUTOCLICK_REVERT_TO_LEFT_CLICK =
+ "accessibility_autoclick_revert_to_left_click";
+
+ /**
* Whether or not larger size icons are used for the pointer of mouse/trackpad for
* accessibility.
* (0 = false, 1 = true)
@@ -9455,24 +9493,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
@@ -10591,9 +10611,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
@@ -11137,6 +11211,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.
@@ -12504,6 +12584,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
*/
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 ea01fc98eda0..0b2239aa42b2 100644
--- a/core/java/android/security/advancedprotection/AdvancedProtectionManager.java
+++ b/core/java/android/security/advancedprotection/AdvancedProtectionManager.java
@@ -16,7 +16,6 @@
package android.security.advancedprotection;
-import static android.app.admin.DevicePolicyIdentifiers.MEMORY_TAGGING_POLICY;
import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK;
import static android.os.UserManager.DISALLOW_CELLULAR_2G;
import static android.os.UserManager.DISALLOW_INSTALL_UNKNOWN_SOURCES_GLOBALLY;
@@ -58,6 +57,11 @@ 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.
+ private static final String MEMORY_TAGGING_POLICY = "memoryTagging";
/**
* Advanced Protection's identifier for setting policies or restrictions in
@@ -340,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);
@@ -359,8 +364,7 @@ public final class AdvancedProtectionManager {
featureId = FEATURE_ID_DISALLOW_INSTALL_UNKNOWN_SOURCES;
} else if (DISALLOW_CELLULAR_2G.equals(identifier)) {
featureId = FEATURE_ID_DISALLOW_CELLULAR_2G;
- } else if (android.app.admin.flags.Flags.setMtePolicyCoexistence() && MEMORY_TAGGING_POLICY
- .equals(identifier)) {
+ } else if (MEMORY_TAGGING_POLICY.equals(identifier)) {
featureId = FEATURE_ID_ENABLE_MTE;
} else {
throw new UnsupportedOperationException("Unsupported identifier: " + identifier);
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/dreams/IDreamManager.aidl b/core/java/android/service/dreams/IDreamManager.aidl
index 3ca9d937b0ca..fdacd600b9f5 100644
--- a/core/java/android/service/dreams/IDreamManager.aidl
+++ b/core/java/android/service/dreams/IDreamManager.aidl
@@ -59,4 +59,6 @@ interface IDreamManager {
float screenBrightnessFloat, int screenBrightnessInt,
boolean useNormalBrightnessForDoze);
oneway void finishSelfOneway(in IBinder token, boolean immediate);
+ @JavaPassthrough(annotation="@android.annotation.RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS)")
+ void setScreensaverEnabled(boolean enabled);
}
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/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/service/wallpaper/WallpaperService.java b/core/java/android/service/wallpaper/WallpaperService.java
index 41a64e22e058..744cdf6629e7 100644
--- a/core/java/android/service/wallpaper/WallpaperService.java
+++ b/core/java/android/service/wallpaper/WallpaperService.java
@@ -1323,14 +1323,13 @@ public abstract class WallpaperService extends Service {
redrawNeeded ? 1 : 0));
return;
}
-
- final int transformHint = SurfaceControl.rotationToBufferTransform(
- (mDisplay.getInstallOrientation() + mDisplay.getRotation()) % 4);
- mSurfaceControl.setTransformHint(transformHint);
WindowLayout.computeSurfaceSize(mLayout, maxBounds, mWidth, mHeight,
mWinFrames.frame, false /* dragResizing */, mSurfaceSize);
if (mSurfaceControl.isValid()) {
+ final int transformHint = SurfaceControl.rotationToBufferTransform(
+ (mDisplay.getInstallOrientation() + mDisplay.getRotation()) % 4);
+ mSurfaceControl.setTransformHint(transformHint);
if (mBbqSurfaceControl == null) {
mBbqSurfaceControl = new SurfaceControl.Builder()
.setName("Wallpaper BBQ wrapper")
diff --git a/core/java/android/text/OWNERS b/core/java/android/text/OWNERS
index 0935ffd9dd81..b493ef7009d9 100644
--- a/core/java/android/text/OWNERS
+++ b/core/java/android/text/OWNERS
@@ -4,7 +4,6 @@ grantapher@google.com
halilibo@google.com
haoyuchang@google.com
justinghan@google.com
-klippenstein@google.com
nona@google.com
seanmcq@google.com
siyamed@google.com
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/ImeBackAnimationController.java b/core/java/android/view/ImeBackAnimationController.java
index 19e0913fbc65..c39456a3b172 100644
--- a/core/java/android/view/ImeBackAnimationController.java
+++ b/core/java/android/view/ImeBackAnimationController.java
@@ -16,7 +16,11 @@
package android.view;
+import static android.view.InsetsController.ANIMATION_DURATION_SYNC_IME_MS;
+import static android.view.InsetsController.ANIMATION_DURATION_UNSYNC_IME_MS;
import static android.view.InsetsController.ANIMATION_TYPE_USER;
+import static android.view.InsetsController.FAST_OUT_LINEAR_IN_INTERPOLATOR;
+import static android.view.InsetsController.SYNC_IME_INTERPOLATOR;
import static android.view.WindowInsets.Type.ime;
import static android.view.WindowManager.LayoutParams.SOFT_INPUT_ADJUST_PAN;
import static android.view.WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE;
@@ -56,8 +60,6 @@ public class ImeBackAnimationController implements OnBackAnimationCallback {
private static final Interpolator BACK_GESTURE = new BackGestureInterpolator();
private static final Interpolator EMPHASIZED_DECELERATE = new PathInterpolator(
0.05f, 0.7f, 0.1f, 1f);
- private static final Interpolator STANDARD_ACCELERATE = new PathInterpolator(0.3f, 0f, 1f, 1f);
-
private final InsetsController mInsetsController;
private final ViewRootImpl mViewRoot;
private WindowInsetsAnimationController mWindowInsetsAnimationController = null;
@@ -183,8 +185,21 @@ public class ImeBackAnimationController implements OnBackAnimationCallback {
float targetProgress = triggerBack ? 1f : 0f;
mPostCommitAnimator = ValueAnimator.ofFloat(
BACK_GESTURE.getInterpolation(mLastProgress) * PEEK_FRACTION, targetProgress);
- mPostCommitAnimator.setInterpolator(
- triggerBack ? STANDARD_ACCELERATE : EMPHASIZED_DECELERATE);
+ Interpolator interpolator;
+ long duration;
+ if (triggerBack && mViewRoot.mView.hasWindowInsetsAnimationCallback()
+ && mWindowInsetsAnimationController.getShownStateInsets().bottom != 0) {
+ interpolator = SYNC_IME_INTERPOLATOR;
+ duration = ANIMATION_DURATION_SYNC_IME_MS;
+ } else if (triggerBack) {
+ interpolator = FAST_OUT_LINEAR_IN_INTERPOLATOR;
+ duration = ANIMATION_DURATION_UNSYNC_IME_MS;
+ } else {
+ interpolator = EMPHASIZED_DECELERATE;
+ duration = POST_COMMIT_CANCEL_DURATION_MS;
+ }
+ mPostCommitAnimator.setInterpolator(interpolator);
+ mPostCommitAnimator.setDuration(duration);
mPostCommitAnimator.addUpdateListener(animation -> {
if (mWindowInsetsAnimationController != null) {
setInterpolatedProgress((float) animation.getAnimatedValue());
@@ -207,8 +222,6 @@ public class ImeBackAnimationController implements OnBackAnimationCallback {
reset();
}
});
- mPostCommitAnimator.setDuration(
- triggerBack ? POST_COMMIT_DURATION_MS : POST_COMMIT_CANCEL_DURATION_MS);
mPostCommitAnimator.start();
if (triggerBack) {
mInsetsController.setPredictiveBackImeHideAnimInProgress(true);
diff --git a/core/java/android/view/InsetsController.java b/core/java/android/view/InsetsController.java
index 462c5c630759..6f346bdae70a 100644
--- a/core/java/android/view/InsetsController.java
+++ b/core/java/android/view/InsetsController.java
@@ -235,8 +235,8 @@ public class InsetsController implements WindowInsetsController, InsetsAnimation
private static final int ANIMATION_DELAY_DIM_MS = 500;
- private static final int ANIMATION_DURATION_SYNC_IME_MS = 285;
- private static final int ANIMATION_DURATION_UNSYNC_IME_MS = 200;
+ static final int ANIMATION_DURATION_SYNC_IME_MS = 285;
+ static final int ANIMATION_DURATION_UNSYNC_IME_MS = 200;
private static final int PENDING_CONTROL_TIMEOUT_MS = 2000;
@@ -256,11 +256,11 @@ public class InsetsController implements WindowInsetsController, InsetsAnimation
return 1f - SYSTEM_BARS_ALPHA_INTERPOLATOR.getInterpolation(innerFraction);
}
};
- private static final Interpolator SYNC_IME_INTERPOLATOR =
+ static final Interpolator SYNC_IME_INTERPOLATOR =
new PathInterpolator(0.2f, 0f, 0f, 1f);
private static final Interpolator LINEAR_OUT_SLOW_IN_INTERPOLATOR =
new PathInterpolator(0, 0, 0.2f, 1f);
- private static final Interpolator FAST_OUT_LINEAR_IN_INTERPOLATOR =
+ static final Interpolator FAST_OUT_LINEAR_IN_INTERPOLATOR =
new PathInterpolator(0.4f, 0f, 1f, 1f);
/** Visible for WindowManagerWrapper */
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/OWNERS b/core/java/android/view/OWNERS
index 80484a6328e0..3353923292e1 100644
--- a/core/java/android/view/OWNERS
+++ b/core/java/android/view/OWNERS
@@ -3,7 +3,6 @@
romainguy@google.com
adamp@google.com
aurimas@google.com
-nduca@google.com
sumir@google.com
ogunwale@google.com
jjaggi@google.com
diff --git a/core/java/android/view/SurfaceView.java b/core/java/android/view/SurfaceView.java
index b98f4db5dce6..4f6c730857a8 100644
--- a/core/java/android/view/SurfaceView.java
+++ b/core/java/android/view/SurfaceView.java
@@ -2247,6 +2247,27 @@ public class SurfaceView extends View implements ViewRootImpl.SurfaceChangedCall
t.reparent(sc, mBlastSurfaceControl).show(sc);
}
+ /**
+ * Populates a {@link ViewStructure} for content capture.
+ *
+ * <p>If {@link #setSecure(boolean)} has been enabled, will add a property to the
+ * {@link android.app.assist.AssistStructure.ViewNode} to indicate that content will not
+ * be available for this part of the screen.
+ *
+ * @hide
+ */
+ @Override
+ protected void onProvideStructure(@NonNull ViewStructure structure,
+ @ViewStructureType int viewFor, int flags) {
+ super.onProvideStructure(structure, viewFor, flags);
+ if (android.app.contextualsearch.flags.Flags.reportSecureSurfacesInAssistStructure()) {
+ if ((mSurfaceFlags & SurfaceControl.SECURE) != 0) {
+ structure.getExtras().putBoolean(
+ ViewStructure.EXTRA_CONTAINS_SECURE_LAYERS, true);
+ }
+ }
+ }
+
/** @hide */
@Override
public void onInitializeAccessibilityNodeInfoInternal(AccessibilityNodeInfo info) {
diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java
index c048d7987515..f50d77e6aead 100644
--- a/core/java/android/view/View.java
+++ b/core/java/android/view/View.java
@@ -34463,7 +34463,10 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
*/
@FlaggedApi(android.app.jank.Flags.FLAG_DETAILED_APP_JANK_METRICS_API)
public void reportAppJankStats(@NonNull AppJankStats appJankStats) {
- getRootView().reportAppJankStats(appJankStats);
+ View rootView = getRootView();
+ if (rootView == this) return;
+
+ rootView.reportAppJankStats(appJankStats);
}
/**
@@ -34471,6 +34474,10 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
* @hide
*/
public @Nullable JankTracker getJankTracker() {
- return getRootView().getJankTracker();
+ View rootView = getRootView();
+ if (rootView == this) {
+ return null;
+ }
+ return rootView.getJankTracker();
}
}
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 7fd7be8585a4..7dc96f21b5ae 100644
--- a/core/java/android/view/ViewRootImpl.java
+++ b/core/java/android/view/ViewRootImpl.java
@@ -153,6 +153,7 @@ import android.annotation.UiContext;
import android.app.ActivityManager;
import android.app.ActivityThread;
import android.app.ResourcesManager;
+import android.app.UiModeManager;
import android.app.WindowConfiguration;
import android.app.compat.CompatChanges;
import android.app.servertransaction.WindowStateTransactionItem;
@@ -196,6 +197,7 @@ import android.hardware.display.DisplayManagerGlobal;
import android.hardware.input.InputManagerGlobal;
import android.hardware.input.InputSettings;
import android.media.AudioManager;
+import android.net.Uri;
import android.os.Binder;
import android.os.Build;
import android.os.Bundle;
@@ -472,8 +474,6 @@ public final class ViewRootImpl implements ViewParent,
@Nullable
private ContentObserver mForceInvertObserver;
- private static final int INVALID_VALUE = Integer.MIN_VALUE;
- private int mForceInvertEnabled = INVALID_VALUE;
/**
* Callback for notifying about global configuration changes.
*/
@@ -555,6 +555,8 @@ public final class ViewRootImpl implements ViewParent,
@UiContext
public final Context mContext;
+ private UiModeManager mUiModeManager;
+
@UnsupportedAppUsage
final IWindowSession mWindowSession;
@NonNull Display mDisplay;
@@ -1804,23 +1806,6 @@ public final class ViewRootImpl implements ViewParent,
}
}
- private boolean isForceInvertEnabled() {
- if (mForceInvertEnabled == INVALID_VALUE) {
- reloadForceInvertEnabled();
- }
- return mForceInvertEnabled == 1;
- }
-
- private void reloadForceInvertEnabled() {
- if (forceInvertColor()) {
- mForceInvertEnabled = Settings.Secure.getIntForUser(
- mContext.getContentResolver(),
- Settings.Secure.ACCESSIBILITY_FORCE_INVERT_COLOR_ENABLED,
- /* def= */ 0,
- UserHandle.myUserId());
- }
- }
-
/**
* Register any kind of listeners if setView was success.
*/
@@ -1851,24 +1836,30 @@ public final class ViewRootImpl implements ViewParent,
eventsToBeRegistered,
mBasePackageName);
+ // LINT.IfChange(fi_cb)
if (forceInvertColor()) {
if (mForceInvertObserver == null) {
mForceInvertObserver = new ContentObserver(mHandler) {
@Override
public void onChange(boolean selfChange) {
- reloadForceInvertEnabled();
updateForceDarkMode();
}
};
- mContext.getContentResolver().registerContentObserver(
- Settings.Secure.getUriFor(
- Settings.Secure.ACCESSIBILITY_FORCE_INVERT_COLOR_ENABLED
- ),
- false,
- mForceInvertObserver,
- UserHandle.myUserId());
+ final Uri[] urisToObserve = {
+ Settings.Secure.getUriFor(
+ Settings.Secure.ACCESSIBILITY_FORCE_INVERT_COLOR_ENABLED),
+ Settings.Secure.getUriFor(Settings.Secure.UI_NIGHT_MODE)
+ };
+ for (Uri uri : urisToObserve) {
+ mContext.getContentResolver().registerContentObserver(
+ uri,
+ false,
+ mForceInvertObserver,
+ UserHandle.myUserId());
+ }
}
}
+ // LINT.ThenChange(/services/core/java/com/android/server/UiModeManagerService.java:fi_cb)
}
/**
@@ -2073,30 +2064,48 @@ public final class ViewRootImpl implements ViewParent,
return getConfiguration().uiMode & Configuration.UI_MODE_NIGHT_MASK;
}
- /** Returns true if force dark should be enabled according to various settings */
+ /**
+ * Determines the type of force dark to apply, considering force inversion, system night mode,
+ * and app-specific settings (including developer opt-outs).
+ *
+ * @return A {@link ForceDarkType.ForceDarkTypeDef} constant indicating the force dark type.
+ */
@VisibleForTesting
public @ForceDarkType.ForceDarkTypeDef int determineForceDarkType() {
- if (forceInvertColor()) {
- // Force invert ignores all developer opt-outs.
- // 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 the force invert
- // setting will be enabled at the same time dark theme is in the Settings app.
- if (isForceInvertEnabled()) {
- return ForceDarkType.FORCE_INVERT_COLOR_DARK;
+ TypedArray a = mContext.obtainStyledAttributes(R.styleable.Theme);
+ try {
+ if (forceInvertColor()) {
+ // Force invert ignores all developer opt-outs.
+ // 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) {
+ final boolean isLightTheme =
+ a.getBoolean(R.styleable.Theme_isLightTheme, false);
+ // TODO: b/372558459 - Also check the background ColorDrawable color lightness
+ // TODO: b/368725782 - Use hwui color area detection instead of / in
+ // addition to these heuristics.
+ if (isLightTheme) {
+ return ForceDarkType.FORCE_INVERT_COLOR_DARK;
+ } else {
+ return ForceDarkType.NONE;
+ }
+ }
}
- }
- boolean useAutoDark = getNightMode() == Configuration.UI_MODE_NIGHT_YES;
-
- if (useAutoDark) {
- boolean forceDarkAllowedDefault =
- SystemProperties.getBoolean(ThreadedRenderer.DEBUG_FORCE_DARK, false);
- TypedArray a = mContext.obtainStyledAttributes(R.styleable.Theme);
- useAutoDark = a.getBoolean(R.styleable.Theme_isLightTheme, true)
- && a.getBoolean(R.styleable.Theme_forceDarkAllowed, forceDarkAllowedDefault);
+ boolean useAutoDark = getNightMode() == Configuration.UI_MODE_NIGHT_YES;
+ if (useAutoDark) {
+ boolean forceDarkAllowedDefault =
+ SystemProperties.getBoolean(ThreadedRenderer.DEBUG_FORCE_DARK, false);
+ useAutoDark = a.getBoolean(R.styleable.Theme_isLightTheme, true)
+ && a.getBoolean(R.styleable.Theme_forceDarkAllowed,
+ forceDarkAllowedDefault);
+ }
+ return useAutoDark ? ForceDarkType.FORCE_DARK : ForceDarkType.NONE;
+ } finally {
a.recycle();
}
- return useAutoDark ? ForceDarkType.FORCE_DARK : ForceDarkType.NONE;
}
private void updateForceDarkMode() {
@@ -9401,6 +9410,13 @@ 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);
@@ -13639,4 +13655,11 @@ public final class ViewRootImpl implements ViewParent,
ThreadedRenderer.preInitBufferAllocator();
}
}
+
+ /**
+ * @hide
+ */
+ public Choreographer getChoreographer() {
+ return mChoreographer;
+ }
}
diff --git a/core/java/android/view/ViewStructure.java b/core/java/android/view/ViewStructure.java
index 43a946a234ba..53953a9379df 100644
--- a/core/java/android/view/ViewStructure.java
+++ b/core/java/android/view/ViewStructure.java
@@ -77,6 +77,19 @@ public abstract class ViewStructure {
"android.view.ViewStructure.extra.FIRST_ACTIVE_POSITION";
/**
+ * Key used for confirming whether the view draws graphics containing secure layers.
+ *
+ * <p>Secure layers cannot be read back into main memory and will show up as blank regions
+ * in assist screenshots.
+ *
+ * @see android.view.SurfaceControl#SECURE
+ *
+ * @hide
+ */
+ public static final String EXTRA_CONTAINS_SECURE_LAYERS =
+ "android.view.ViewStructure.extra.CONTAINS_SECURE_LAYERS";
+
+ /**
* Key used for writing the type of the view that generated the virtual structure of its
* children.
*
diff --git a/core/java/android/view/WindowManager.java b/core/java/android/view/WindowManager.java
index 196ae5e59fa7..83dc79beb75e 100644
--- a/core/java/android/view/WindowManager.java
+++ b/core/java/android/view/WindowManager.java
@@ -631,6 +631,12 @@ public interface WindowManager extends ViewManager {
int TRANSIT_FLAG_AOD_APPEARING = (1 << 15); // 0x8000
/**
+ * Transition flag: Indicates that the task shouldn't move to front when launching the activity.
+ * @hide
+ */
+ int TRANSIT_FLAG_AVOID_MOVE_TO_FRONT = (1 << 16); // 0x10000
+
+ /**
* @hide
*/
@IntDef(flag = true, prefix = { "TRANSIT_FLAG_" }, value = {
@@ -650,6 +656,7 @@ public interface WindowManager extends ViewManager {
TRANSIT_FLAG_KEYGUARD_UNOCCLUDING,
TRANSIT_FLAG_PHYSICAL_DISPLAY_SWITCH,
TRANSIT_FLAG_AOD_APPEARING,
+ TRANSIT_FLAG_AVOID_MOVE_TO_FRONT,
})
@Retention(RetentionPolicy.SOURCE)
@interface TransitionFlags {}
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 d267c9451d1a..e43fb48527a1 100644
--- a/core/java/android/view/accessibility/AccessibilityManager.java
+++ b/core/java/android/view/accessibility/AccessibilityManager.java
@@ -163,6 +163,9 @@ public final class AccessibilityManager {
/** @hide */
public static final boolean AUTOCLICK_IGNORE_MINOR_CURSOR_MOVEMENT_DEFAULT = false;
+ /** @hide */
+ public static final boolean AUTOCLICK_REVERT_TO_LEFT_CLICK_DEFAULT = true;
+
/**
* Activity action: Launch UI to manage which accessibility service or feature is assigned
* to the navigation bar Accessibility button.
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/inspector/OWNERS b/core/java/android/view/inspector/OWNERS
index 705f4b342d42..f3450344ea81 100644
--- a/core/java/android/view/inspector/OWNERS
+++ b/core/java/android/view/inspector/OWNERS
@@ -2,5 +2,4 @@ romainguy@google.com
alanv@google.com
adamp@google.com
aurimas@google.com
-nduca@google.com
sumir@google.com
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/TEST_MAPPING b/core/java/android/webkit/TEST_MAPPING
index 38580595dc2d..c9b54760491a 100644
--- a/core/java/android/webkit/TEST_MAPPING
+++ b/core/java/android/webkit/TEST_MAPPING
@@ -17,15 +17,6 @@
"exclude-annotation": "android.test.FlakyTest"
}
]
- },
- {
- "name": "GtsWebViewHostTestCases",
- "keywords": ["internal"],
- "options": [
- {
- "exclude-annotation": "android.test.FlakyTest"
- }
- ]
}
]
}
diff --git a/core/java/android/webkit/WebChromeClient.java b/core/java/android/webkit/WebChromeClient.java
index 1baf3b82ca50..c5e558f1f475 100644
--- a/core/java/android/webkit/WebChromeClient.java
+++ b/core/java/android/webkit/WebChromeClient.java
@@ -531,8 +531,9 @@ public class WebChromeClient {
* the chosen file(s). WebView can access all files that your app can access.
* In case the file(s) are chosen through an untrusted source such as a third-party
* app, it is your own app's responsibility to check what the returned Uris
- * refer to before calling the <code>filePathCallback</code>. See
- * {@link #createIntent} and {@link #parseResult} for more details.</p>
+ * refer to before calling the {@code filePathCallback}. See
+ * {@link FileChooserParams#createIntent} and {@link FileChooserParams#parseResult} for more
+ * details.
*
* @param webView The WebView instance that is initiating the request.
* @param filePathCallback Invoke this callback to supply the list of paths to files to upload,
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/webkit/WebSettings.java b/core/java/android/webkit/WebSettings.java
index 14b208aecf5c..ab7a4f289d73 100644
--- a/core/java/android/webkit/WebSettings.java
+++ b/core/java/android/webkit/WebSettings.java
@@ -1458,13 +1458,14 @@ public abstract class WebSettings {
public abstract void setNeedInitialFocus(boolean flag);
/**
- * Sets the priority of the Render thread. Unlike the other settings, this
+ * Sets the CPU scheduling priority of the Render thread. Unlike the other settings, this
* one only needs to be called once per process. The default value is
* {@link RenderPriority#NORMAL}.
*
* @param priority the priority
- * @deprecated It is not recommended to adjust thread priorities, and this will
- * not be supported in future versions.
+ * @deprecated This is no longer supported. See {@link WebView#setRendererPriorityPolicy} if you
+ * instead want to control how freely the system should kill the renderer process
+ * under low memory conditions.
*/
@Deprecated
public abstract void setRenderPriority(RenderPriority priority);
diff --git a/core/java/android/webkit/WebView.java b/core/java/android/webkit/WebView.java
index b6663992b851..5e22c287140a 100644
--- a/core/java/android/webkit/WebView.java
+++ b/core/java/android/webkit/WebView.java
@@ -2287,26 +2287,53 @@ public class WebView extends AbsoluteLayout
public @interface RendererPriority {}
/**
- * The renderer associated with this WebView is bound with
- * {@link Context#BIND_WAIVE_PRIORITY}. At this priority level
- * {@link WebView} renderers will be strong targets for out of memory
- * killing.
+ * This is the lowest binding priority for the WebView renderer process. This is equivalent to
+ * {@link Context#BIND_WAIVE_PRIORITY}. At this priority level {@link WebView} renderers will be
+ * frequent targets for being killed when the system is running low on memory.
+ *
+ * <p>If using this priority, we recommend handling the {@link
+ * WebViewClient#onRenderProcessGone} callback to recover from low memory kills as well as other
+ * types of renderer crashes.
*
- * Use with {@link #setRendererPriorityPolicy}.
+ * @see #setRendererPriorityPolicy
+ * @see #getRendererPriorityPolicy
+ * @see RenderProcessGoneDetail#rendererPriorityAtExit
*/
public static final int RENDERER_PRIORITY_WAIVED = 0;
+
/**
- * The renderer associated with this WebView is bound with
- * the default priority for services.
+ * This is the medium binding priority for the WebView renderer process. This is equivalent to
+ * the standard priority used by the system for calls to {@link
+ * Context#bindService(Intent,android.content.ServiceConnection,int)} when no flags are
+ * provided. At this priority level {@link WebView} renderers will be slightly more likely
+ * targets for being killed when the system is running low on memory.
*
- * Use with {@link #setRendererPriorityPolicy}.
+ * <p>If using this priority, we recommend handling the {@link
+ * WebViewClient#onRenderProcessGone} callback to recover from low memory kills as well as other
+ * types of renderer crashes.
+ *
+ * @see #setRendererPriorityPolicy
+ * @see #getRendererPriorityPolicy
+ * @see RenderProcessGoneDetail#rendererPriorityAtExit
*/
public static final int RENDERER_PRIORITY_BOUND = 1;
+
/**
- * The renderer associated with this WebView is bound with
- * {@link Context#BIND_IMPORTANT}.
+ * This is the highest binding priority for the WebView renderer process, and the default value
+ * WebView uses to bind to the renderer process. This is equivalent to {@link
+ * Context#BIND_IMPORTANT}. At this priority level {@link WebView} renderers are less likely to
+ * be killed when the system is running low on memory and will have the same priority as your
+ * app's main process.
+ *
+ * <p>It's still possible for the renderer process to be killed when the system is running low
+ * on memory, however specifying this priority makes this situation less likely. It's also still
+ * possible for the renderer process to crash for other reasons. You may optionally still choose
+ * to handle {@link WebViewClient#onRenderProcessGone} in order to recover from a killed or
+ * crashed renderer process.
*
- * Use with {@link #setRendererPriorityPolicy}.
+ * @see #setRendererPriorityPolicy
+ * @see #getRendererPriorityPolicy
+ * @see RenderProcessGoneDetail#rendererPriorityAtExit
*/
public static final int RENDERER_PRIORITY_IMPORTANT = 2;
@@ -2316,7 +2343,7 @@ public class WebView extends AbsoluteLayout
* process renderer should be considered to be a target for OOM
* killing.
*
- * Because a renderer can be associated with more than one
+ * <p>Because a renderer can be associated with more than one
* WebView, the final priority it is computed as the maximum of
* any attached WebViews. When a WebView is destroyed it will
* cease to be considerered when calculating the renderer
@@ -2324,7 +2351,7 @@ public class WebView extends AbsoluteLayout
* the priority of the renderer will be reduced to
* {@link #RENDERER_PRIORITY_WAIVED}.
*
- * The default policy is to set the priority to
+ * <p>The default policy is to set the priority to
* {@link #RENDERER_PRIORITY_IMPORTANT} regardless of visibility,
* and this should not be changed unless the caller also handles
* renderer crashes with
@@ -2338,6 +2365,8 @@ public class WebView extends AbsoluteLayout
* when this WebView is not visible, it will be treated as
* if it had requested a priority of
* {@link #RENDERER_PRIORITY_WAIVED}.
+ * @see #getRendererPriorityPolicy
+ * @see RenderProcessGoneDetail#rendererPriorityAtExit
*/
public void setRendererPriorityPolicy(
@RendererPriority int rendererRequestedPriority,
@@ -2349,6 +2378,8 @@ public class WebView extends AbsoluteLayout
* Get the requested renderer priority for this WebView.
*
* @return the requested renderer priority policy.
+ * @see #setRendererPriorityPolicy
+ * @see RenderProcessGoneDetail#rendererPriorityAtExit
*/
@InspectableProperty(hasAttributeId = false, enumMapping = {
@InspectableProperty.EnumEntry(name = "waived", value = RENDERER_PRIORITY_WAIVED),
diff --git a/core/java/android/widget/TextView.java b/core/java/android/widget/TextView.java
index 5e828ba46df7..99fe0cbdca25 100644
--- a/core/java/android/widget/TextView.java
+++ b/core/java/android/widget/TextView.java
@@ -5211,11 +5211,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
*/
@Nullable
public String getFontVariationSettings() {
- if (Flags.typefaceRedesignReadonly()) {
- return mTextPaint.getFontVariationOverride();
- } else {
- return mTextPaint.getFontVariationSettings();
- }
+ return mTextPaint.getFontVariationSettings();
}
/**
@@ -5571,10 +5567,10 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
Math.clamp(400 + mFontWeightAdjustment,
FontStyle.FONT_WEIGHT_MIN, FontStyle.FONT_WEIGHT_MAX)));
}
- mTextPaint.setFontVariationOverride(
+ mTextPaint.setFontVariationSettings(
FontVariationAxis.toFontVariationSettings(axes));
} else {
- mTextPaint.setFontVariationOverride(fontVariationSettings);
+ mTextPaint.setFontVariationSettings(fontVariationSettings);
}
effective = true;
} else {
diff --git a/core/java/android/window/DesktopExperienceFlags.java b/core/java/android/window/DesktopExperienceFlags.java
index e0c48b03dad8..866c16cb566d 100644
--- a/core/java/android/window/DesktopExperienceFlags.java
+++ b/core/java/android/window/DesktopExperienceFlags.java
@@ -47,26 +47,32 @@ 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_FOCUS_IN_SHELL_TRANSITIONS(Flags::enableDisplayFocusInShellTransitions, true),
+ 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_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 e74a87536c57..17165cdcf7b1 100644
--- a/core/java/android/window/DesktopModeFlags.java
+++ b/core/java/android/window/DesktopModeFlags.java
@@ -53,14 +53,18 @@ 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(
+ Flags::enableDesktopOpeningDeeplinkMinimizeAnimationBugfix, false),
ENABLE_DESKTOP_RECENTS_TRANSITIONS_CORNERS_BUGFIX(
Flags::enableDesktopRecentsTransitionsCornersBugfix, false),
ENABLE_DESKTOP_SKIP_COMPAT_UI_EDUCATION_IN_DESKTOP_MODE_BUGFIX(
@@ -90,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),
@@ -104,6 +109,7 @@ 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(
@@ -112,6 +118,8 @@ public enum DesktopModeFlags {
ENABLE_RESIZING_METRICS(Flags::enableResizingMetrics, true),
ENABLE_RESTORE_TO_PREVIOUS_SIZE_FROM_DESKTOP_IMMERSIVE(
Flags::enableRestoreToPreviousSizeFromDesktopImmersive, true),
+ ENABLE_SHELL_INITIAL_BOUNDS_REGRESSION_BUG_FIX(
+ Flags::enableShellInitialBoundsRegressionBugFix, false),
ENABLE_START_LAUNCH_TRANSITION_FROM_TASKBAR_BUGFIX(
Flags::enableStartLaunchTransitionFromTaskbarBugfix, true),
ENABLE_TASKBAR_OVERFLOW(Flags::enableTaskbarOverflow, false),
@@ -122,16 +130,22 @@ public enum DesktopModeFlags {
ENABLE_TILE_RESIZING(Flags::enableTileResizing, true),
ENABLE_TOP_VISIBLE_ROOT_TASK_PER_USER_TRACKING(Flags::enableTopVisibleRootTaskPerUserTracking,
true),
+ ENABLE_VISUAL_INDICATOR_IN_TRANSITION_BUGFIX(
+ Flags::enableVisualIndicatorInTransitionBugfix, false),
ENABLE_WINDOWING_DYNAMIC_INITIAL_BOUNDS(Flags::enableWindowingDynamicInitialBounds, true),
ENABLE_WINDOWING_EDGE_DRAG_RESIZE(Flags::enableWindowingEdgeDragResize, true),
ENABLE_WINDOWING_SCALED_RESIZING(Flags::enableWindowingScaledResizing, true),
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),
// go/keep-sorted end
;
diff --git a/core/java/android/window/TransitionInfo.java b/core/java/android/window/TransitionInfo.java
index 63c55ad6a889..be4edc3b6ec5 100644
--- a/core/java/android/window/TransitionInfo.java
+++ b/core/java/android/window/TransitionInfo.java
@@ -29,6 +29,7 @@ import static android.view.Display.INVALID_DISPLAY;
import static android.view.WindowManager.LayoutParams.ROTATION_ANIMATION_UNSPECIFIED;
import static android.view.WindowManager.TRANSIT_CHANGE;
import static android.view.WindowManager.TRANSIT_CLOSE;
+import static android.view.WindowManager.TRANSIT_FLAG_AOD_APPEARING;
import static android.view.WindowManager.TRANSIT_FLAG_KEYGUARD_APPEARING;
import static android.view.WindowManager.TRANSIT_FLAG_KEYGUARD_GOING_AWAY;
import static android.view.WindowManager.TRANSIT_NONE;
@@ -375,7 +376,8 @@ public final class TransitionInfo implements Parcelable {
*/
public boolean hasChangesOrSideEffects() {
return !mChanges.isEmpty() || isKeyguardGoingAway()
- || (mFlags & TRANSIT_FLAG_KEYGUARD_APPEARING) != 0;
+ || (mFlags & TRANSIT_FLAG_KEYGUARD_APPEARING) != 0
+ || (mFlags & TRANSIT_FLAG_AOD_APPEARING) != 0;
}
/**
diff --git a/core/java/android/window/WindowContainerTransaction.java b/core/java/android/window/WindowContainerTransaction.java
index 1156503cf8e8..ea345a5ef46a 100644
--- a/core/java/android/window/WindowContainerTransaction.java
+++ b/core/java/android/window/WindowContainerTransaction.java
@@ -688,12 +688,6 @@ public final class WindowContainerTransaction implements Parcelable {
@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);
}
@@ -714,10 +708,6 @@ public final class WindowContainerTransaction implements Parcelable {
*/
@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");
}
@@ -1973,13 +1963,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;
}
@@ -2127,17 +2110,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:
diff --git a/core/java/android/window/flags/lse_desktop_experience.aconfig b/core/java/android/window/flags/lse_desktop_experience.aconfig
index 355a87d72203..e706af999117 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"
@@ -38,6 +49,29 @@ flag {
}
flag {
+ name: "enable_shell_initial_bounds_regression_bug_fix"
+ namespace: "lse_desktop_experience"
+ description: "Enables fix for Shell initial bounds regression, forcing core to calculate /n"
+ "initial bounds in desktop launch params while respecting cascading position /n"
+ "passed by Shell."
+ bug: "396075922"
+ metadata {
+ purpose: PURPOSE_BUGFIX
+ }
+}
+
+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"
@@ -142,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"
@@ -791,6 +835,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."
@@ -804,7 +868,7 @@ flag {
name: "enable_taskbar_overflow"
namespace: "lse_desktop_experience"
description: "Show recent apps in the taskbar overflow."
- bug: "368119679"
+ bug: "375627272"
}
flag {
@@ -815,13 +879,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 {
@@ -852,4 +913,35 @@ flag {
metadata {
purpose: PURPOSE_BUGFIX
}
-} \ No newline at end of file
+}
+
+flag {
+ name: "enable_visual_indicator_in_transition_bugfix"
+ namespace: "lse_desktop_experience"
+ description: "Enables bugfix to move visual drop-zone indicator to transition root, so it can't be shown after the transition."
+ bug: "392826275"
+ 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"
+}
diff --git a/core/java/android/window/flags/windowing_frontend.aconfig b/core/java/android/window/flags/windowing_frontend.aconfig
index 0e19eb2d3219..3927c713e500 100644
--- a/core/java/android/window/flags/windowing_frontend.aconfig
+++ b/core/java/android/window/flags/windowing_frontend.aconfig
@@ -51,6 +51,17 @@ flag {
}
flag {
+ name: "use_cached_insets_for_display_switch"
+ namespace: "windowing_frontend"
+ description: "Reduce intermediate insets changes for display switch"
+ bug: "266197298"
+ is_fixed_read_only: true
+ metadata {
+ purpose: PURPOSE_BUGFIX
+ }
+}
+
+flag {
name: "edge_to_edge_by_default"
namespace: "windowing_frontend"
description: "Make app go edge-to-edge by default when targeting SDK 35 or greater"
@@ -75,6 +86,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"
@@ -482,4 +501,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 9f768f065fe9..cbeaeda65fca 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"
@@ -127,17 +137,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"
@@ -152,4 +151,15 @@ flag {
metadata {
purpose: PURPOSE_BUGFIX
}
-} \ No newline at end of file
+}
+
+flag {
+ namespace: "windowing_sdk"
+ name: "cleanup_dispatch_pending_transactions_remote_exception"
+ description: "Refactor to cleanup for RemoteException from dispatchPendingTransactions"
+ bug: "323801078"
+ is_fixed_read_only: true
+ metadata {
+ purpose: PURPOSE_BUGFIX
+ }
+}
diff --git a/core/java/com/android/internal/accessibility/dialog/AccessibilityTargetHelper.java b/core/java/com/android/internal/accessibility/dialog/AccessibilityTargetHelper.java
index 6498c53a63e2..a2d6c2b51835 100644
--- a/core/java/com/android/internal/accessibility/dialog/AccessibilityTargetHelper.java
+++ b/core/java/com/android/internal/accessibility/dialog/AccessibilityTargetHelper.java
@@ -215,7 +215,6 @@ public final class AccessibilityTargetHelper {
Settings.Secure.ACCESSIBILITY_DISPLAY_INVERSION_ENABLED);
targets.add(colorInversion);
- // TODO(b/394683600): Update Icon with the autoclick asset.
final ToggleAllowListingFeatureTarget autoclick =
new ToggleAllowListingFeatureTarget(context,
shortcutType,
@@ -224,7 +223,7 @@ public final class AccessibilityTargetHelper {
AUTOCLICK_COMPONENT_NAME.flattenToString(),
uid,
context.getString(R.string.autoclick_feature_name),
- context.getDrawable(R.drawable.ic_accessibility_generic),
+ context.getDrawable(R.drawable.ic_accessibility_autoclick),
Settings.Secure.ACCESSIBILITY_AUTOCLICK_ENABLED);
targets.add(autoclick);
diff --git a/core/java/com/android/internal/config/appcloning/OWNERS b/core/java/com/android/internal/config/appcloning/OWNERS
index 0645a8c54414..9369438deb07 100644
--- a/core/java/com/android/internal/config/appcloning/OWNERS
+++ b/core/java/com/android/internal/config/appcloning/OWNERS
@@ -1,3 +1,2 @@
# Bug component: 1207885
jigarthakkar@google.com
-saumyap@google.com \ No newline at end of file
diff --git a/core/java/com/android/internal/infra/OWNERS b/core/java/com/android/internal/infra/OWNERS
index 45503582b2c5..e69de29bb2d1 100644
--- a/core/java/com/android/internal/infra/OWNERS
+++ b/core/java/com/android/internal/infra/OWNERS
@@ -1,6 +0,0 @@
-per-file AndroidFuture.java = eugenesusla@google.com
-per-file RemoteStream.java = eugenesusla@google.com
-per-file PerUser.java = eugenesusla@google.com
-per-file ServiceConnector.java = eugenesusla@google.com
-per-file AndroidFuture.aidl = eugenesusla@google.com
-per-file IAndroidFuture.aidl = eugenesusla@google.com \ No newline at end of file
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/os/DEBUG_STORE_OWNERS b/core/java/com/android/internal/os/DEBUG_STORE_OWNERS
new file mode 100644
index 000000000000..c8e22b70ff77
--- /dev/null
+++ b/core/java/com/android/internal/os/DEBUG_STORE_OWNERS
@@ -0,0 +1,2 @@
+benmiles@google.com
+mohamadmahmoud@google.com
diff --git a/core/java/com/android/internal/os/DebugStore.java b/core/java/com/android/internal/os/DebugStore.java
index 4c45feed8511..3dca786a82af 100644
--- a/core/java/com/android/internal/os/DebugStore.java
+++ b/core/java/com/android/internal/os/DebugStore.java
@@ -20,6 +20,7 @@ import android.annotation.Nullable;
import android.compat.annotation.UnsupportedAppUsage;
import android.content.Intent;
import android.content.pm.ServiceInfo;
+import android.util.Log;
import com.android.internal.annotations.VisibleForTesting;
@@ -43,6 +44,9 @@ import java.util.Objects;
* @hide
*/
public class DebugStore {
+ private static final boolean DEBUG_EVENTS = false;
+ private static final String TAG = "DebugStore";
+
private static DebugStoreNative sDebugStoreNative = new DebugStoreNativeImpl();
@UnsupportedAppUsage
@@ -120,7 +124,7 @@ public class DebugStore {
* @param receiverClassName The class name of the broadcast receiver.
*/
@UnsupportedAppUsage
- public static void recordGoAsync(String receiverClassName) {
+ public static void recordGoAsync(int pendingResultId) {
sDebugStoreNative.recordEvent(
"GoAsync",
List.of(
@@ -128,8 +132,8 @@ public class DebugStore {
Thread.currentThread().getName(),
"tid",
String.valueOf(Thread.currentThread().getId()),
- "rcv",
- Objects.toString(receiverClassName)));
+ "prid",
+ Integer.toHexString(pendingResultId)));
}
/**
@@ -138,7 +142,7 @@ public class DebugStore {
* @param receiverClassName The class of the broadcast receiver that completed the operation.
*/
@UnsupportedAppUsage
- public static void recordFinish(String receiverClassName) {
+ public static void recordFinish(int pendingResultId) {
sDebugStoreNative.recordEvent(
"Finish",
List.of(
@@ -146,9 +150,10 @@ public class DebugStore {
Thread.currentThread().getName(),
"tid",
String.valueOf(Thread.currentThread().getId()),
- "rcv",
- Objects.toString(receiverClassName)));
+ "prid",
+ Integer.toHexString(pendingResultId)));
}
+
/**
* Records the completion of a long-running looper message.
*
@@ -172,21 +177,92 @@ public class DebugStore {
/**
- * Records the reception of a broadcast.
+ * Records the reception of a broadcast by a manifest-declared receiver.
*
* @param intent The Intent associated with the broadcast.
* @return A unique ID for the recorded event.
*/
@UnsupportedAppUsage
- public static long recordBroadcastHandleReceiver(@Nullable Intent intent) {
+ public static long recordBroadcastReceive(@Nullable Intent intent, int pendingResultId) {
+ return sDebugStoreNative.beginEvent(
+ "BcRcv",
+ List.of(
+ "tname",
+ Thread.currentThread().getName(),
+ "tid",
+ String.valueOf(Thread.currentThread().getId()),
+ "act",
+ Objects.toString(intent != null ? intent.getAction() : null),
+ "cmp",
+ Objects.toString(intent != null ? intent.getComponent() : null),
+ "pkg",
+ Objects.toString(intent != null ? intent.getPackage() : null),
+ "prid",
+ Integer.toHexString(pendingResultId)));
+ }
+
+ /**
+ * Records the reception of a broadcast by a context-registered receiver.
+ *
+ * @param intent The Intent associated with the broadcast.
+ * @param pendingResultId The object ID of the PendingResult associated with the broadcast.
+ * @return A unique ID for the recorded event.
+ */
+ @UnsupportedAppUsage
+ public static long recordBroadcastReceiveReg(@Nullable Intent intent, int pendingResultId) {
+ return sDebugStoreNative.beginEvent(
+ "BcRcvReg",
+ List.of(
+ "tname",
+ Thread.currentThread().getName(),
+ "tid",
+ String.valueOf(Thread.currentThread().getId()),
+ "act",
+ Objects.toString(intent != null ? intent.getAction() : null),
+ "cmp",
+ Objects.toString(intent != null ? intent.getComponent() : null),
+ "pkg",
+ Objects.toString(intent != null ? intent.getPackage() : null),
+ "prid",
+ Integer.toHexString(pendingResultId)));
+ }
+
+ /**
+ * Records the binding of an application.
+ *
+ * @return A unique ID for the recorded event.
+ */
+ @UnsupportedAppUsage
+ public static long recordHandleBindApplication() {
+ return sDebugStoreNative.beginEvent("BindApp", List.of());
+ }
+
+ /**
+ * Records the scheduling of a receiver.
+ *
+ * @return A unique ID for the recorded event.
+ */
+ @UnsupportedAppUsage
+ public static long recordScheduleReceiver() {
return sDebugStoreNative.beginEvent(
- "HandleReceiver",
+ "SchRcv",
List.of(
"tname", Thread.currentThread().getName(),
- "tid", String.valueOf(Thread.currentThread().getId()),
- "act", Objects.toString(intent != null ? intent.getAction() : null),
- "cmp", Objects.toString(intent != null ? intent.getComponent() : null),
- "pkg", Objects.toString(intent != null ? intent.getPackage() : null)));
+ "tid", String.valueOf(Thread.currentThread().getId())));
+ }
+
+ /**
+ * Records the scheduling of a registered receiver.
+ *
+ * @return A unique ID for the recorded event.
+ */
+ @UnsupportedAppUsage
+ public static long recordScheduleRegisteredReceiver() {
+ return sDebugStoreNative.beginEvent(
+ "SchRcvReg",
+ List.of(
+ "tname", Thread.currentThread().getName(),
+ "tid", String.valueOf(Thread.currentThread().getId())));
}
/**
@@ -225,18 +301,48 @@ public class DebugStore {
private static class DebugStoreNativeImpl implements DebugStoreNative {
@Override
public long beginEvent(String eventName, List<String> attributes) {
- return DebugStore.beginEventNative(eventName, attributes);
+ long id = DebugStore.beginEventNative(eventName, attributes);
+ if (DEBUG_EVENTS) {
+ Log.i(
+ TAG,
+ "beginEvent: " + id + " " + eventName + " " + attributeString(attributes));
+ }
+ return id;
}
@Override
public void endEvent(long id, List<String> attributes) {
+ if (DEBUG_EVENTS) {
+ Log.i(TAG, "endEvent: " + id + " " + attributeString(attributes));
+ }
DebugStore.endEventNative(id, attributes);
}
@Override
public void recordEvent(String eventName, List<String> attributes) {
+ if (DEBUG_EVENTS) {
+ Log.i(TAG, "recordEvent: " + eventName + " " + attributeString(attributes));
+ }
DebugStore.recordEventNative(eventName, attributes);
}
+
+ /**
+ * Returns a string like "[key1=foo, key2=bar]"
+ */
+ private String attributeString(List<String> attributes) {
+ StringBuilder sb = new StringBuilder().append("[");
+
+ for (int i = 0; i < attributes.size(); i++) {
+ sb.append(attributes.get(i));
+
+ if (i % 2 == 0) {
+ sb.append("=");
+ } else if (i < attributes.size() - 1) {
+ sb.append(", ");
+ }
+ }
+ return sb.append("]").toString();
+ }
}
private static native long beginEventNative(String eventName, List<String> attributes);
diff --git a/core/java/com/android/internal/os/OWNERS b/core/java/com/android/internal/os/OWNERS
index ffd4499f929a..a5aa9618db32 100644
--- a/core/java/com/android/internal/os/OWNERS
+++ b/core/java/com/android/internal/os/OWNERS
@@ -2,6 +2,7 @@ per-file *Power* = file:/services/core/java/com/android/server/power/OWNERS
per-file *Zygote* = file:/ZYGOTE_OWNERS
per-file *Cpu* = file:CPU_OWNERS
per-file *Binder* = file:BINDER_OWNERS
+per-file *DebugStore* = file:DEBUG_STORE_OWNERS
per-file *BinaryTransparency* = file:/core/java/android/transparency/OWNERS
# BatteryStats
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/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/util/function/pooled/OWNERS b/core/java/com/android/internal/util/function/pooled/OWNERS
index da723b3b67da..e69de29bb2d1 100644
--- a/core/java/com/android/internal/util/function/pooled/OWNERS
+++ b/core/java/com/android/internal/util/function/pooled/OWNERS
@@ -1 +0,0 @@
-eugenesusla@google.com \ No newline at end of file
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/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/OWNERS b/core/java/com/android/internal/widget/remotecompose/OWNERS
index e163474bccb9..c606744df150 100644
--- a/core/java/com/android/internal/widget/remotecompose/OWNERS
+++ b/core/java/com/android/internal/widget/remotecompose/OWNERS
@@ -1,6 +1,5 @@
nicolasroard@google.com
hoford@google.com
-jnichol@google.com
sihua@google.com
sunnygoyal@google.com
oscarad@google.com
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 b8503da2c09b..796ea8d1538f 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/CoreDocument.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/CoreDocument.java
@@ -65,7 +65,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.2f;
+ static final float BUILD = 0.3f;
@NonNull ArrayList<Operation> mOperations = new ArrayList<>();
@@ -411,7 +411,7 @@ public class CoreDocument implements Serializable {
@Override
public void serialize(MapSerializer serializer) {
serializer
- .add("type", "CoreDocument")
+ .addType("CoreDocument")
.add("width", mWidth)
.add("height", mHeight)
.add("operations", mOperations);
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 09ec40271f4d..9cbafab07a43 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/Operations.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/Operations.java
@@ -100,6 +100,7 @@ import com.android.internal.widget.remotecompose.core.operations.layout.managers
import com.android.internal.widget.remotecompose.core.operations.layout.managers.CollapsibleColumnLayout;
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.RowLayout;
import com.android.internal.widget.remotecompose.core.operations.layout.managers.StateLayout;
import com.android.internal.widget.remotecompose.core.operations.layout.managers.TextLayout;
@@ -231,6 +232,7 @@ public class Operations {
public static final int LAYOUT_ROOT = 200;
public static final int LAYOUT_CONTENT = 201;
public static final int LAYOUT_BOX = 202;
+ public static final int LAYOUT_FIT_BOX = 176;
public static final int LAYOUT_ROW = 203;
public static final int LAYOUT_COLLAPSIBLE_ROW = 230;
public static final int LAYOUT_COLUMN = 204;
@@ -391,6 +393,7 @@ public class Operations {
map.put(LAYOUT_ROOT, RootLayoutComponent::read);
map.put(LAYOUT_CONTENT, LayoutComponentContent::read);
map.put(LAYOUT_BOX, BoxLayout::read);
+ map.put(LAYOUT_FIT_BOX, FitBoxLayout::read);
map.put(LAYOUT_COLUMN, ColumnLayout::read);
map.put(LAYOUT_COLLAPSIBLE_COLUMN, CollapsibleColumnLayout::read);
map.put(LAYOUT_ROW, RowLayout::read);
diff --git a/core/java/com/android/internal/widget/remotecompose/core/PaintOperation.java b/core/java/com/android/internal/widget/remotecompose/core/PaintOperation.java
index f355676be63e..c27ee3284b9c 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/PaintOperation.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/PaintOperation.java
@@ -17,11 +17,13 @@ package com.android.internal.widget.remotecompose.core;
import android.annotation.NonNull;
+import com.android.internal.widget.remotecompose.core.serialize.Serializable;
+
/**
* PaintOperation interface, used for operations aimed at painting (while any operation _can_ paint,
* this make it a little more explicit)
*/
-public abstract class PaintOperation extends Operation {
+public abstract class PaintOperation extends Operation implements Serializable {
@Override
public void apply(@NonNull RemoteContext context) {
diff --git a/core/java/com/android/internal/widget/remotecompose/core/Platform.java b/core/java/com/android/internal/widget/remotecompose/core/Platform.java
index 6073de662cdb..3fff86c38b2e 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/Platform.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/Platform.java
@@ -47,6 +47,14 @@ public interface Platform {
int getImageHeight(@NonNull Object image);
/**
+ * Returns true if the platform-specific image object has format ALPHA_8
+ *
+ * @param image platform-specific image object
+ * @return whether or not the platform-specific image object has format ALPHA_8
+ */
+ boolean isAlpha8Image(@NonNull Object image);
+
+ /**
* Converts a platform-specific path object into a platform-independent float buffer
*
* @param path
@@ -110,6 +118,11 @@ public interface Platform {
}
@Override
+ public boolean isAlpha8Image(@NonNull Object image) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
public float[] pathToFloatArray(@NonNull Object path) {
throw new UnsupportedOperationException();
}
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 e75bd30b381d..c249adf5bd58 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/RemoteComposeBuffer.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/RemoteComposeBuffer.java
@@ -98,6 +98,7 @@ import com.android.internal.widget.remotecompose.core.operations.layout.managers
import com.android.internal.widget.remotecompose.core.operations.layout.managers.CollapsibleColumnLayout;
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.RowLayout;
import com.android.internal.widget.remotecompose.core.operations.layout.managers.StateLayout;
import com.android.internal.widget.remotecompose.core.operations.layout.managers.TextLayout;
@@ -281,13 +282,7 @@ public class RemoteComposeBuffer {
int dstRight,
int dstBottom,
@Nullable String contentDescription) {
- int imageId = mRemoteComposeState.dataGetId(image);
- if (imageId == -1) {
- imageId = mRemoteComposeState.cacheData(image);
- byte[] data = mPlatform.imageToByteArray(image); // todo: potential npe
- BitmapData.apply(
- mBuffer, imageId, imageWidth, imageHeight, data); // todo: potential npe
- }
+ int imageId = storeBitmap(image);
int contentDescriptionId = 0;
if (contentDescription != null) {
contentDescriptionId = addText(contentDescription);
@@ -443,16 +438,7 @@ public class RemoteComposeBuffer {
float right,
float bottom,
@Nullable String contentDescription) {
- int imageId = mRemoteComposeState.dataGetId(image);
- if (imageId == -1) {
- imageId = mRemoteComposeState.cacheData(image);
- byte[] data = mPlatform.imageToByteArray(image); // todo: potential npe
- int imageWidth = mPlatform.getImageWidth(image);
- int imageHeight = mPlatform.getImageHeight(image);
-
- BitmapData.apply(
- mBuffer, imageId, imageWidth, imageHeight, data); // todo: potential npe
- }
+ int imageId = storeBitmap(image);
addDrawBitmap(imageId, left, top, right, bottom, contentDescription);
}
@@ -523,15 +509,7 @@ public class RemoteComposeBuffer {
int scaleType,
float scaleFactor,
@Nullable String contentDescription) {
- int imageId = mRemoteComposeState.dataGetId(image);
- if (imageId == -1) {
- imageId = mRemoteComposeState.cacheData(image);
- byte[] data = mPlatform.imageToByteArray(image); // todo: potential npe
- int imageWidth = mPlatform.getImageWidth(image);
- int imageHeight = mPlatform.getImageHeight(image);
-
- BitmapData.apply(mBuffer, imageId, imageWidth, imageHeight, data); // todo: potential pe
- }
+ int imageId = storeBitmap(image);
int contentDescriptionId = 0;
if (contentDescription != null) {
contentDescriptionId = addText(contentDescription);
@@ -559,16 +537,7 @@ public class RemoteComposeBuffer {
* @return id of the image useful with
*/
public int addBitmap(@NonNull Object image) {
- int imageId = mRemoteComposeState.dataGetId(image);
- if (imageId == -1) {
- imageId = mRemoteComposeState.cacheData(image);
- byte[] data = mPlatform.imageToByteArray(image); // tODO: potential npe
- int imageWidth = mPlatform.getImageWidth(image);
- int imageHeight = mPlatform.getImageHeight(image);
-
- BitmapData.apply(mBuffer, imageId, imageWidth, imageHeight, data);
- }
- return imageId;
+ return storeBitmap(image);
}
/**
@@ -578,18 +547,7 @@ public class RemoteComposeBuffer {
* @return id of the image useful with
*/
public int addBitmap(@NonNull Object image, @NonNull String name) {
- int imageId = mRemoteComposeState.dataGetId(image);
- if (imageId == -1) {
- imageId = mRemoteComposeState.cacheData(image);
- byte[] data = mPlatform.imageToByteArray(image); // todo: potential npe
- int imageWidth = mPlatform.getImageWidth(image);
- int imageHeight = mPlatform.getImageHeight(image);
-
- BitmapData.apply(mBuffer, imageId, imageWidth, imageHeight, data);
- setBitmapName(imageId, name);
- }
-
- return imageId;
+ return storeBitmap(image);
}
/**
@@ -1393,7 +1351,7 @@ public class RemoteComposeBuffer {
* @return the id of the command representing long
*/
public int addLong(long value) {
- int id = mRemoteComposeState.cacheData(value);
+ int id = mRemoteComposeState.nextId();
LongConstant.apply(mBuffer, id, value);
return id;
}
@@ -1405,7 +1363,7 @@ public class RemoteComposeBuffer {
* @return the id
*/
public int addBoolean(boolean value) {
- int id = mRemoteComposeState.cacheData(value);
+ int id = mRemoteComposeState.nextId();
BooleanConstant.apply(mBuffer, id, value);
return id;
}
@@ -1821,33 +1779,14 @@ public class RemoteComposeBuffer {
}
/**
- * This defines the name of the color given the id.
- *
- * @param id of the color
- * @param name Name of the color
- */
- public void setColorName(int id, @NonNull String name) {
- NamedVariable.apply(mBuffer, id, NamedVariable.COLOR_TYPE, name);
- }
-
- /**
- * This defines the name of the string given the id
- *
- * @param id of the string
- * @param name name of the string
- */
- public void setStringName(int id, @NonNull String name) {
- NamedVariable.apply(mBuffer, id, NamedVariable.STRING_TYPE, name);
- }
-
- /**
- * This defines the name of the float given the id
+ * This defines the name of a type of given object
*
* @param id of the float
* @param name name of the float
+ * @param type the type of variable NamedVariable.COLOR_TYPE, STRING_TYPE, etc
*/
- public void setFloatName(int id, String name) {
- NamedVariable.apply(mBuffer, id, NamedVariable.FLOAT_TYPE, name);
+ public void setNamedVariable(int id, @NonNull String name, int type) {
+ NamedVariable.apply(mBuffer, id, type, name);
}
/**
@@ -2139,6 +2078,19 @@ public class RemoteComposeBuffer {
}
/**
+ * Add a fitbox start tag
+ *
+ * @param componentId component id
+ * @param animationId animation id
+ * @param horizontal horizontal alignment
+ * @param vertical vertical alignment
+ */
+ public void addFitBoxStart(int componentId, int animationId, int horizontal, int vertical) {
+ mLastComponentId = getComponentId(componentId);
+ FitBoxLayout.apply(mBuffer, mLastComponentId, animationId, horizontal, vertical);
+ }
+
+ /**
* Add a row start tag
*
* @param componentId component id
@@ -2439,4 +2391,35 @@ public class RemoteComposeBuffer {
public void drawComponentContent() {
DrawContent.apply(mBuffer);
}
+
+ /**
+ * Ensures the bitmap is stored.
+ *
+ * @param image the bitbap to store
+ * @return the id of the bitmap
+ */
+ private int storeBitmap(Object image) {
+ int imageId = mRemoteComposeState.dataGetId(image);
+ if (imageId == -1) {
+ imageId = mRemoteComposeState.cacheData(image);
+ byte[] data = mPlatform.imageToByteArray(image); // todo: potential npe
+ short imageWidth = (short) mPlatform.getImageWidth(image);
+ short imageHeight = (short) mPlatform.getImageHeight(image);
+ if (mPlatform.isAlpha8Image(image)) {
+ BitmapData.apply(
+ mBuffer,
+ imageId,
+ BitmapData.TYPE_PNG_ALPHA_8,
+ imageWidth,
+ BitmapData.ENCODING_INLINE,
+ imageHeight,
+ data); // todo: potential npe
+ } else {
+ BitmapData.apply(
+ mBuffer, imageId, imageWidth, imageHeight, data); // todo: potential npe
+ }
+ }
+
+ return imageId;
+ }
}
diff --git a/core/java/com/android/internal/widget/remotecompose/core/RemoteComposeState.java b/core/java/com/android/internal/widget/remotecompose/core/RemoteComposeState.java
index 363b82bdf70c..83c06197db9c 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/RemoteComposeState.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/RemoteComposeState.java
@@ -27,6 +27,7 @@ import com.android.internal.widget.remotecompose.core.operations.utilities.IntMa
import com.android.internal.widget.remotecompose.core.operations.utilities.NanMap;
import java.util.ArrayList;
+import java.util.Arrays;
import java.util.HashMap;
/**
@@ -36,7 +37,7 @@ import java.util.HashMap;
public class RemoteComposeState implements CollectionsAccess {
public static final int START_ID = 42;
// private static final int MAX_FLOATS = 500;
- private static final int MAX_COLORS = 200;
+ private static int sMaxColors = 200;
private static final int MAX_DATA = 1000;
private final IntMap<Object> mIntDataMap = new IntMap<>();
@@ -52,7 +53,7 @@ public class RemoteComposeState implements CollectionsAccess {
private final IntMap<Object> mPathMap = new IntMap<>();
private final IntMap<float[]> mPathData = new IntMap<>();
- private final boolean[] mColorOverride = new boolean[MAX_COLORS];
+ private boolean[] mColorOverride = new boolean[sMaxColors];
@NonNull private final IntMap<ArrayAccess> mCollectionMap = new IntMap<>();
private final boolean[] mDataOverride = new boolean[MAX_DATA];
@@ -318,7 +319,7 @@ public class RemoteComposeState implements CollectionsAccess {
* @param color
*/
public void updateColor(int id, int color) {
- if (mColorOverride[id]) {
+ if (id < sMaxColors && mColorOverride[id]) {
return;
}
mColorMap.put(id, color);
@@ -342,6 +343,10 @@ public class RemoteComposeState implements CollectionsAccess {
* @param color
*/
public void overrideColor(int id, int color) {
+ if (id >= sMaxColors) {
+ sMaxColors *= 2;
+ mColorOverride = Arrays.copyOf(mColorOverride, sMaxColors);
+ }
mColorOverride[id] = true;
mColorMap.put(id, color);
updateListeners(id);
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 622f0c8d12b7..c6b17e4116d6 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/RemoteContext.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/RemoteContext.java
@@ -211,6 +211,14 @@ public abstract class RemoteContext {
public abstract void clearNamedFloatOverride(String floatName);
/**
+ * Set the value of a named long. This modifies the content of a LongConstant
+ *
+ * @param name the name of the float to override
+ * @param value Override the default float
+ */
+ public abstract void setNamedLong(String name, long value);
+
+ /**
* Set the value of a named Object. This overrides the Object in the document
*
* @param dataName the name of the Object to override
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 13e6f3859933..255d7a46e49e 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
@@ -72,6 +72,9 @@ public class BitmapData extends Operation implements SerializableToString, Seria
/** The data is encoded as RAW 8888 bit */
public static final short TYPE_RAW8888 = 3;
+ /** The data is encoded as PNG_8888 but decoded as ALPHA_8 */
+ public static final short TYPE_PNG_ALPHA_8 = 4;
+
/**
* create a bitmap structure
*
@@ -136,6 +139,15 @@ public class BitmapData extends Operation implements SerializableToString, Seria
}
/**
+ * The type of the image
+ *
+ * @return the type of the image
+ */
+ public int getType() {
+ return mType;
+ }
+
+ /**
* Add the image to the document
*
* @param buffer document to write to
@@ -195,6 +207,21 @@ public class BitmapData extends Operation implements SerializableToString, Seria
int imageId = buffer.readInt();
int width = buffer.readInt();
int height = buffer.readInt();
+ int type;
+ if (width > 0xffff) {
+ type = width >> 16;
+ width = width & 0xffff;
+ } else {
+ type = TYPE_PNG_8888;
+ }
+
+ int encoding;
+ if (height > 0xffff) {
+ encoding = height >> 16;
+ height = height & 0xffff;
+ } else {
+ encoding = ENCODING_INLINE;
+ }
if (width < 1
|| height < 1
|| height > MAX_IMAGE_DIMENSION
@@ -202,7 +229,10 @@ public class BitmapData extends Operation implements SerializableToString, Seria
throw new RuntimeException("Dimension of image is invalid " + width + "x" + height);
}
byte[] bitmap = buffer.readBuffer();
- operations.add(new BitmapData(imageId, width, height, bitmap));
+ BitmapData bitmapData = new BitmapData(imageId, width, height, bitmap);
+ bitmapData.mType = (short) type;
+ bitmapData.mEncoding = (short) encoding;
+ operations.add(bitmapData);
}
/**
@@ -244,7 +274,7 @@ public class BitmapData extends Operation implements SerializableToString, Seria
@Override
public void serialize(MapSerializer serializer) {
serializer
- .add("type", CLASS_NAME)
+ .addType(CLASS_NAME)
.add("imageId", mImageId)
.add("imageWidth", mImageWidth)
.add("imageHeight", mImageHeight)
@@ -275,6 +305,8 @@ public class BitmapData extends Operation implements SerializableToString, Seria
return "TYPE_RAW8";
case TYPE_RAW8888:
return "TYPE_RAW8888";
+ case TYPE_PNG_ALPHA_8:
+ return "TYPE_PNG_ALPHA_8";
default:
return "TYPE_INVALID";
}
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/BitmapFontData.java b/core/java/com/android/internal/widget/remotecompose/core/operations/BitmapFontData.java
index 078ce981d243..70bda6d1c0fa 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/BitmapFontData.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/BitmapFontData.java
@@ -221,6 +221,6 @@ public class BitmapFontData extends Operation implements Serializable {
@Override
public void serialize(MapSerializer serializer) {
- serializer.add("type", CLASS_NAME).add("id", mId);
+ serializer.addType(CLASS_NAME).add("id", mId);
}
}
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/ClickArea.java b/core/java/com/android/internal/widget/remotecompose/core/operations/ClickArea.java
index 00ac9c28f362..7a8373bf7c7d 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/ClickArea.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/ClickArea.java
@@ -237,7 +237,7 @@ public class ClickArea extends Operation
@Override
public void serialize(MapSerializer serializer) {
serializer
- .add("type", CLASS_NAME)
+ .addType(CLASS_NAME)
.add("id", mId)
.add("contentDescriptionId", mContentDescription)
.add("left", mLeft, mOutLeft)
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/ClipPath.java b/core/java/com/android/internal/widget/remotecompose/core/operations/ClipPath.java
index e7dc405b542e..03bdb70a9289 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/ClipPath.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/ClipPath.java
@@ -133,7 +133,7 @@ public class ClipPath extends PaintOperation implements Serializable {
@Override
public void serialize(MapSerializer serializer) {
- serializer.add("type", CLASS_NAME).add("id", mId).add("regionOp", regionOpToString());
+ serializer.addType(CLASS_NAME).add("id", mId).add("regionOp", regionOpToString());
}
String regionOpToString() {
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/ClipRect.java b/core/java/com/android/internal/widget/remotecompose/core/operations/ClipRect.java
index 1646146bd21d..8b1e96a691a6 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/ClipRect.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/ClipRect.java
@@ -118,6 +118,6 @@ public class ClipRect extends DrawBase4 {
@Override
public void serialize(MapSerializer serializer) {
- serialize(serializer, "left", "top", "right", "bottom").add("type", CLASS_NAME);
+ serialize(serializer, "left", "top", "right", "bottom").addType(CLASS_NAME);
}
}
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/ColorConstant.java b/core/java/com/android/internal/widget/remotecompose/core/operations/ColorConstant.java
index 333ffaae8289..7b12f4a9cf15 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/ColorConstant.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/ColorConstant.java
@@ -133,7 +133,7 @@ public class ColorConstant extends Operation implements Serializable {
@Override
public void serialize(MapSerializer serializer) {
serializer
- .add("type", CLASS_NAME)
+ .addType(CLASS_NAME)
.add("color", Utils.colorInt(mColor))
.add("colorId", mColorId);
}
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/ColorExpression.java b/core/java/com/android/internal/widget/remotecompose/core/operations/ColorExpression.java
index d5af7914607a..25323a8a818d 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/ColorExpression.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/ColorExpression.java
@@ -507,7 +507,7 @@ public class ColorExpression extends Operation implements VariableSupport, Seria
@Override
public void serialize(MapSerializer serializer) {
- serializer.add("type", CLASS_NAME).add("id", mId);
+ serializer.addType(CLASS_NAME).add("id", mId);
switch (mMode) {
case COLOR_COLOR_INTERPOLATE:
case ID_COLOR_INTERPOLATE:
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/ComponentValue.java b/core/java/com/android/internal/widget/remotecompose/core/operations/ComponentValue.java
index cd13d2534266..5335e4fa76ca 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/ComponentValue.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/ComponentValue.java
@@ -171,7 +171,7 @@ public class ComponentValue extends Operation implements SerializableToString, S
@Override
public void serialize(MapSerializer serializer) {
serializer
- .add("type", CLASS_NAME)
+ .addType(CLASS_NAME)
.add("valueId", mValueId)
.add("componentValueType", typeToString(mType))
.add("componentId", mComponentID);
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/DataListFloat.java b/core/java/com/android/internal/widget/remotecompose/core/operations/DataListFloat.java
index fb3abdbb0626..20bebaa1ddb2 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/DataListFloat.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/DataListFloat.java
@@ -148,8 +148,9 @@ public class DataListFloat extends Operation implements VariableSupport, ArrayAc
return mValues.length;
}
+ @SuppressWarnings("JdkImmutableCollections")
@Override
public void serialize(MapSerializer serializer) {
- serializer.add("type", CLASS_NAME).add("id", mId).add("values", List.of(mValues));
+ serializer.addType(CLASS_NAME).add("id", mId).add("values", List.of(mValues));
}
}
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/DataListIds.java b/core/java/com/android/internal/widget/remotecompose/core/operations/DataListIds.java
index 58fd74fdcf3e..af660f3d42c9 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/DataListIds.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/DataListIds.java
@@ -150,8 +150,9 @@ public class DataListIds extends Operation implements VariableSupport, ArrayAcce
return 0;
}
+ @SuppressWarnings("JdkImmutableCollections")
@Override
public void serialize(MapSerializer serializer) {
- serializer.add("type", CLASS_NAME).add("id", mId).add("ids", List.of(mIds));
+ serializer.addType(CLASS_NAME).add("id", mId).add("ids", List.of(mIds));
}
}
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/DrawArc.java b/core/java/com/android/internal/widget/remotecompose/core/operations/DrawArc.java
index a427836b6c5f..5024164651f5 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/DrawArc.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/DrawArc.java
@@ -141,6 +141,6 @@ public class DrawArc extends DrawBase6 {
@Override
public void serialize(MapSerializer serializer) {
serialize(serializer, "left", "top", "right", "bottom", "startAngle", "sweepAngle")
- .add("type", CLASS_NAME);
+ .addType(CLASS_NAME);
}
}
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/DrawBitmap.java b/core/java/com/android/internal/widget/remotecompose/core/operations/DrawBitmap.java
index 40d3bede0912..e3b53a125a63 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/DrawBitmap.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/DrawBitmap.java
@@ -28,6 +28,7 @@ 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.serialize.MapSerializer;
import java.util.List;
@@ -185,4 +186,16 @@ public class DrawBitmap extends PaintOperation implements VariableSupport {
public void paint(@NonNull PaintContext context) {
context.drawBitmap(mId, mOutputLeft, mOutputTop, mOutputRight, mOutputBottom);
}
+
+ @Override
+ public void serialize(MapSerializer serializer) {
+ serializer
+ .addType(CLASS_NAME)
+ .add("imageId", mId)
+ .add("contentDescriptionId", mDescriptionId)
+ .add("left", mLeft, mOutputLeft)
+ .add("top", mTop, mOutputTop)
+ .add("right", mRight, mOutputRight)
+ .add("bottom", mBottom, mOutputBottom);
+ }
}
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/DrawBitmapFontText.java b/core/java/com/android/internal/widget/remotecompose/core/operations/DrawBitmapFontText.java
index 258988e8b00a..bff87fda2fd9 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/DrawBitmapFontText.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/DrawBitmapFontText.java
@@ -28,6 +28,7 @@ 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 java.util.List;
@@ -229,4 +230,16 @@ public class DrawBitmapFontText extends PaintOperation implements VariableSuppor
xPos = xPos2 + glyph.mMarginRight;
}
}
+
+ @Override
+ public void serialize(MapSerializer serializer) {
+ serializer
+ .addType(CLASS_NAME)
+ .add("textId", mTextID)
+ .add("bitmapFontId", mBitmapFontID)
+ .add("start", mStart)
+ .add("end", mEnd)
+ .add("x", mX, mOutX)
+ .add("y", mY, mOutY);
+ }
}
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/DrawBitmapInt.java b/core/java/com/android/internal/widget/remotecompose/core/operations/DrawBitmapInt.java
index 013dd1ae9db8..39d85af900ee 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/DrawBitmapInt.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/DrawBitmapInt.java
@@ -25,6 +25,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.documentation.DocumentedOperation;
import com.android.internal.widget.remotecompose.core.semantics.AccessibleComponent;
+import com.android.internal.widget.remotecompose.core.serialize.MapSerializer;
import java.util.List;
@@ -230,4 +231,20 @@ public class DrawBitmapInt extends PaintOperation implements AccessibleComponent
mDstBottom,
mContentDescId);
}
+
+ @Override
+ public void serialize(MapSerializer serializer) {
+ serializer
+ .addType(CLASS_NAME)
+ .add("imageId", mImageId)
+ .add("contentDescriptionId", mContentDescId)
+ .add("srcLeft", mSrcLeft)
+ .add("srcTop", mSrcTop)
+ .add("srcRight", mSrcRight)
+ .add("srcBottom", mSrcBottom)
+ .add("dstLeft", mDstLeft)
+ .add("dstTop", mDstTop)
+ .add("dstRight", mDstRight)
+ .add("dstBottom", mDstBottom);
+ }
}
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/DrawBitmapScaled.java b/core/java/com/android/internal/widget/remotecompose/core/operations/DrawBitmapScaled.java
index e1070f97d5aa..827e5692e132 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/DrawBitmapScaled.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/DrawBitmapScaled.java
@@ -28,6 +28,7 @@ import com.android.internal.widget.remotecompose.core.documentation.Documentatio
import com.android.internal.widget.remotecompose.core.documentation.DocumentedOperation;
import com.android.internal.widget.remotecompose.core.operations.utilities.ImageScaling;
import com.android.internal.widget.remotecompose.core.semantics.AccessibleComponent;
+import com.android.internal.widget.remotecompose.core.serialize.MapSerializer;
import java.util.List;
@@ -374,4 +375,46 @@ public class DrawBitmapScaled extends PaintOperation
mContentDescId);
context.restore();
}
+
+ @Override
+ public void serialize(MapSerializer serializer) {
+ serializer
+ .addType(CLASS_NAME)
+ .add("imageId", mImageId)
+ .add("contentDescriptionId", mContentDescId)
+ .add("scaleType", getScaleTypeString())
+ .add("mode", mMode)
+ .add("scaleFactor", mScaleFactor, mOutScaleFactor)
+ .add("srcLeft", mSrcLeft, mOutSrcLeft)
+ .add("srcTop", mSrcTop, mOutSrcTop)
+ .add("srcRight", mSrcRight, mOutSrcRight)
+ .add("srcBottom", mSrcBottom, mOutSrcBottom)
+ .add("dstLeft", mDstLeft, mOutDstLeft)
+ .add("dstTop", mDstTop, mOutDstTop)
+ .add("dstRight", mDstRight, mOutDstRight)
+ .add("dstBottom", mDstBottom, mOutDstBottom);
+ }
+
+ private String getScaleTypeString() {
+ switch (mScaleType) {
+ case SCALE_NONE:
+ return "SCALE_NONE";
+ case SCALE_INSIDE:
+ return "SCALE_INSIDE";
+ case SCALE_FILL_WIDTH:
+ return "SCALE_FILL_WIDTH";
+ case SCALE_FILL_HEIGHT:
+ return "SCALE_FILL_HEIGHT";
+ case SCALE_FIT:
+ return "SCALE_FIT";
+ case SCALE_CROP:
+ return "SCALE_CROP";
+ case SCALE_FILL_BOUNDS:
+ return "SCALE_FILL_BOUNDS";
+ case SCALE_FIXED_SCALE:
+ return "SCALE_FIXED_SCALE";
+ default:
+ return "INVALID_SCALE_TYPE";
+ }
+ }
}
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/DrawCircle.java b/core/java/com/android/internal/widget/remotecompose/core/operations/DrawCircle.java
index dfc89b12de1e..538cbaf97a06 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/DrawCircle.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/DrawCircle.java
@@ -112,6 +112,6 @@ public class DrawCircle extends DrawBase3 {
@Override
public void serialize(MapSerializer serializer) {
- serialize(serializer, "cx", "cy", "radius").add("type", CLASS_NAME);
+ serialize(serializer, "cx", "cy", "radius").addType(CLASS_NAME);
}
}
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 e2e22acbeb8f..4d2a939eb948 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
@@ -114,6 +114,6 @@ public class DrawContent extends PaintOperation implements Serializable {
@Override
public void serialize(MapSerializer serializer) {
- serializer.add("type", CLASS_NAME);
+ serializer.addType(CLASS_NAME);
}
}
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/DrawLine.java b/core/java/com/android/internal/widget/remotecompose/core/operations/DrawLine.java
index 461dece02625..97e5057fb950 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/DrawLine.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/DrawLine.java
@@ -140,6 +140,6 @@ public class DrawLine extends DrawBase4 implements SerializableToString {
@Override
public void serialize(MapSerializer serializer) {
- serialize(serializer, "startX", "startY", "endX", "endY").add("type", CLASS_NAME);
+ serialize(serializer, "startX", "startY", "endX", "endY").addType(CLASS_NAME);
}
}
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/DrawOval.java b/core/java/com/android/internal/widget/remotecompose/core/operations/DrawOval.java
index d0a5adc0e737..5d619baa1328 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/DrawOval.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/DrawOval.java
@@ -110,6 +110,6 @@ public class DrawOval extends DrawBase4 {
@Override
public void serialize(MapSerializer serializer) {
- serialize(serializer, "left", "top", "right", "bottom").add("type", CLASS_NAME);
+ serialize(serializer, "left", "top", "right", "bottom").addType(CLASS_NAME);
}
}
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/DrawPath.java b/core/java/com/android/internal/widget/remotecompose/core/operations/DrawPath.java
index 3fd8bb440d82..6e299275fb24 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/DrawPath.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/DrawPath.java
@@ -112,6 +112,6 @@ public class DrawPath extends PaintOperation implements Serializable {
@Override
public void serialize(MapSerializer serializer) {
- serializer.add("type", CLASS_NAME).add("id", mId).add("start", mStart).add("end", mEnd);
+ serializer.addType(CLASS_NAME).add("id", mId).add("start", mStart).add("end", mEnd);
}
}
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/DrawRect.java b/core/java/com/android/internal/widget/remotecompose/core/operations/DrawRect.java
index f6aa30f5b5dd..15f3ced74652 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/DrawRect.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/DrawRect.java
@@ -106,6 +106,6 @@ public class DrawRect extends DrawBase4 {
@Override
public void serialize(MapSerializer serializer) {
- serialize(serializer, "left", "top", "right", "bottom").add("type", CLASS_NAME);
+ serialize(serializer, "left", "top", "right", "bottom").addType(CLASS_NAME);
}
}
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 67338c14a101..31d9b6ac124d 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
@@ -127,6 +127,6 @@ public class DrawRoundRect extends DrawBase6 {
@Override
public void serialize(MapSerializer serializer) {
serialize(serializer, "left", "top", "right", "bottom", "rx", "sweepAngle")
- .add("type", CLASS_NAME);
+ .addType(CLASS_NAME);
}
}
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/DrawSector.java b/core/java/com/android/internal/widget/remotecompose/core/operations/DrawSector.java
index 78f64a5bcd44..19f16239b9f2 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/DrawSector.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/DrawSector.java
@@ -128,6 +128,6 @@ public class DrawSector extends DrawBase6 {
@Override
public void serialize(MapSerializer serializer) {
serialize(serializer, "left", "top", "right", "bottom", "startAngle", "sweepAngle")
- .add("type", CLASS_NAME);
+ .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 8adba1d2616a..ee1689c21fe3 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
@@ -28,6 +28,7 @@ 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 java.util.List;
@@ -205,4 +206,17 @@ public class DrawText extends PaintOperation implements VariableSupport {
public void paint(@NonNull PaintContext context) {
context.drawTextRun(mTextID, mStart, mEnd, mContextStart, mContextEnd, mOutX, mOutY, mRtl);
}
+
+ @Override
+ public void serialize(MapSerializer serializer) {
+ serializer
+ .addType(CLASS_NAME)
+ .add("start", mStart)
+ .add("end", mEnd)
+ .add("contextStart", mContextStart)
+ .add("contextEnd", mContextEnd)
+ .add("x", mX, mOutX)
+ .add("y", mY, mOutY)
+ .add("rtl", mRtl);
+ }
}
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/DrawTextAnchored.java b/core/java/com/android/internal/widget/remotecompose/core/operations/DrawTextAnchored.java
index 92469d1834ba..1d917d565331 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/DrawTextAnchored.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/DrawTextAnchored.java
@@ -244,7 +244,7 @@ public class DrawTextAnchored extends PaintOperation implements VariableSupport,
@Override
public void serialize(MapSerializer serializer) {
serializer
- .add("type", CLASS_NAME)
+ .addType(CLASS_NAME)
.add("textId", mTextID)
.add("x", mX, mOutX)
.add("y", mY, mOutY)
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/DrawTextOnPath.java b/core/java/com/android/internal/widget/remotecompose/core/operations/DrawTextOnPath.java
index 1f7910ede4b5..f382440360ed 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/DrawTextOnPath.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/DrawTextOnPath.java
@@ -159,7 +159,7 @@ public class DrawTextOnPath extends PaintOperation implements VariableSupport, S
@Override
public void serialize(MapSerializer serializer) {
serializer
- .add("type", CLASS_NAME)
+ .addType(CLASS_NAME)
.add("pathId", mPathId)
.add("textId", mTextId)
.add("vOffset", mVOffset, mOutVOffset)
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/DrawTweenPath.java b/core/java/com/android/internal/widget/remotecompose/core/operations/DrawTweenPath.java
index e2883949022c..4ca995b59f1f 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/DrawTweenPath.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/DrawTweenPath.java
@@ -28,6 +28,7 @@ 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 java.util.List;
@@ -171,4 +172,15 @@ public class DrawTweenPath extends PaintOperation implements VariableSupport {
public void paint(@NonNull PaintContext context) {
context.drawTweenPath(mPath1Id, mPath2Id, mOutTween, mOutStart, mOutStop);
}
+
+ @Override
+ public void serialize(MapSerializer serializer) {
+ serializer
+ .addType(CLASS_NAME)
+ .add("path1Id", mPath1Id)
+ .add("path2Id", mPath2Id)
+ .add("tween", mTween, mOutTween)
+ .add("start", mStart, mOutStart)
+ .add("stop", mStop, mOutStop);
+ }
}
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 7f3c3ed6bcff..233e246d3868 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
@@ -123,6 +123,6 @@ public class FloatConstant extends Operation implements Serializable {
@Override
public void serialize(MapSerializer serializer) {
- serializer.add("type", CLASS_NAME).add("id", mId).add("value", mValue);
+ serializer.addType(CLASS_NAME).add("id", mId).add("value", mValue);
}
}
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/FloatExpression.java b/core/java/com/android/internal/widget/remotecompose/core/operations/FloatExpression.java
index c1fa898ec619..eba201bfb216 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/FloatExpression.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/FloatExpression.java
@@ -160,6 +160,9 @@ public class FloatExpression extends Operation implements VariableSupport, Seria
mLastAnimatedValue = lastComputedValue;
context.loadFloat(mId, lastComputedValue);
context.needsRepaint();
+ if (mFloatAnimation.isPropagate()) {
+ markDirty();
+ }
}
} else if (mSpring != null) {
float lastComputedValue = mSpring.get(t - mLastChange);
@@ -169,8 +172,12 @@ public class FloatExpression extends Operation implements VariableSupport, Seria
context.needsRepaint();
}
} else {
- float v =
- mExp.eval(context.getCollectionsAccess(), mPreCalcValue, mPreCalcValue.length);
+ float v = 0;
+ try {
+ v = mExp.eval(context.getCollectionsAccess(), mPreCalcValue, mPreCalcValue.length);
+ } catch (Exception e) {
+ throw new RuntimeException(this.toString() + " len = " + mPreCalcValue.length, e);
+ }
if (mFloatAnimation != null) {
mFloatAnimation.setTargetValue(v);
}
@@ -344,7 +351,7 @@ public class FloatExpression extends Operation implements VariableSupport, Seria
public void serialize(MapSerializer serializer) {
serializer
.addTags(SerializeTags.EXPRESSION)
- .add("type", CLASS_NAME)
+ .addType(CLASS_NAME)
.add("id", mId)
.addFloatExpressionSrc("srcValues", mSrcValue)
.add("animation", mFloatAnimation);
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/FloatFunctionCall.java b/core/java/com/android/internal/widget/remotecompose/core/operations/FloatFunctionCall.java
index eccc00a18308..85592948438f 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/FloatFunctionCall.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/FloatFunctionCall.java
@@ -32,8 +32,10 @@ import com.android.internal.widget.remotecompose.core.documentation.Documentatio
import com.android.internal.widget.remotecompose.core.documentation.DocumentedOperation;
import com.android.internal.widget.remotecompose.core.operations.utilities.AnimatedFloatExpression;
import com.android.internal.widget.remotecompose.core.operations.utilities.NanMap;
+import com.android.internal.widget.remotecompose.core.serialize.MapSerializer;
import java.util.ArrayList;
+import java.util.Collections;
import java.util.List;
/** This provides the command to call a floatfunction defined in floatfunction */
@@ -182,4 +184,13 @@ public class FloatFunctionCall extends PaintOperation implements VariableSupport
}
mFunction.execute(remoteContext);
}
+
+ @Override
+ public void serialize(MapSerializer serializer) {
+ serializer
+ .addType(CLASS_NAME)
+ .add("id", mId)
+ .add("args", Collections.singletonList(mArgs))
+ .add("outArgs", Collections.singletonList(mOutArgs));
+ }
}
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/ImageAttribute.java b/core/java/com/android/internal/widget/remotecompose/core/operations/ImageAttribute.java
index fb4a5e4848c2..9ef1b3b1ad6a 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/ImageAttribute.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/ImageAttribute.java
@@ -26,7 +26,9 @@ import com.android.internal.widget.remotecompose.core.PaintContext;
import com.android.internal.widget.remotecompose.core.PaintOperation;
import com.android.internal.widget.remotecompose.core.WireBuffer;
import com.android.internal.widget.remotecompose.core.documentation.DocumentationBuilder;
+import com.android.internal.widget.remotecompose.core.serialize.MapSerializer;
+import java.util.Collections;
import java.util.List;
/** Operation to extract meta Attributes from image data objects */
@@ -162,4 +164,25 @@ public class ImageAttribute extends PaintOperation {
break;
}
}
+
+ @Override
+ public void serialize(MapSerializer serializer) {
+ serializer
+ .addType(CLASS_NAME)
+ .add("id", mId)
+ .add("imageId", mImageId)
+ .add("args", Collections.singletonList(mArgs))
+ .addType(typeToString());
+ }
+
+ private String typeToString() {
+ switch (mType) {
+ case IMAGE_WIDTH:
+ return "IMAGE_WIDTH";
+ case IMAGE_HEIGHT:
+ return "IMAGE_HEIGHT";
+ default:
+ return "INVALID_TYPE";
+ }
+ }
}
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/IntegerExpression.java b/core/java/com/android/internal/widget/remotecompose/core/operations/IntegerExpression.java
index 2a5260c0c9b1..53b3c89f3aa8 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/IntegerExpression.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/IntegerExpression.java
@@ -233,7 +233,7 @@ public class IntegerExpression extends Operation implements VariableSupport, Ser
public void serialize(MapSerializer serializer) {
serializer
.addTags(SerializeTags.EXPRESSION)
- .add("type", CLASS_NAME)
+ .addType(CLASS_NAME)
.add("id", mId)
.add("mask", mId)
.addIntExpressionSrc("srcValues", mSrcValue, mMask);
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/MatrixRestore.java b/core/java/com/android/internal/widget/remotecompose/core/operations/MatrixRestore.java
index 64df19d8a04f..d2b38f42e966 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/MatrixRestore.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/MatrixRestore.java
@@ -102,6 +102,6 @@ public class MatrixRestore extends PaintOperation implements Serializable {
@Override
public void serialize(MapSerializer serializer) {
- serializer.add("type", CLASS_NAME);
+ serializer.addType(CLASS_NAME);
}
}
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/MatrixRotate.java b/core/java/com/android/internal/widget/remotecompose/core/operations/MatrixRotate.java
index 9c4df0b54d60..5990b4bdfad7 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/MatrixRotate.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/MatrixRotate.java
@@ -114,6 +114,6 @@ public class MatrixRotate extends DrawBase3 {
@Override
public void serialize(MapSerializer serializer) {
- serialize(serializer, "rotate", "pivotX", "pivotY").add("type", CLASS_NAME);
+ serialize(serializer, "rotate", "pivotX", "pivotY").addType(CLASS_NAME);
}
}
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/MatrixSave.java b/core/java/com/android/internal/widget/remotecompose/core/operations/MatrixSave.java
index 0e6de0db3b28..06da1565bf3a 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/MatrixSave.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/MatrixSave.java
@@ -100,6 +100,6 @@ public class MatrixSave extends PaintOperation implements Serializable {
@Override
public void serialize(MapSerializer serializer) {
- serializer.add("type", CLASS_NAME);
+ serializer.addType(CLASS_NAME);
}
}
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/MatrixScale.java b/core/java/com/android/internal/widget/remotecompose/core/operations/MatrixScale.java
index b6e5cbcf388a..20d406569223 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/MatrixScale.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/MatrixScale.java
@@ -106,6 +106,6 @@ public class MatrixScale extends DrawBase4 {
@Override
public void serialize(MapSerializer serializer) {
- serialize(serializer, "scaleX", "scaleY", "pivotX", "pivotY").add("type", CLASS_NAME);
+ serialize(serializer, "scaleX", "scaleY", "pivotX", "pivotY").addType(CLASS_NAME);
}
}
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/MatrixSkew.java b/core/java/com/android/internal/widget/remotecompose/core/operations/MatrixSkew.java
index f9a589c598aa..6d1c503c3284 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/MatrixSkew.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/MatrixSkew.java
@@ -103,6 +103,6 @@ public class MatrixSkew extends DrawBase2 {
@Override
public void serialize(MapSerializer serializer) {
- serialize(serializer, "skewX", "skewY").add("type", CLASS_NAME);
+ serialize(serializer, "skewX", "skewY").addType(CLASS_NAME);
}
}
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/MatrixTranslate.java b/core/java/com/android/internal/widget/remotecompose/core/operations/MatrixTranslate.java
index de783bfc7ae8..e21f13355679 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/MatrixTranslate.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/MatrixTranslate.java
@@ -102,6 +102,6 @@ public class MatrixTranslate extends DrawBase2 {
@Override
public void serialize(MapSerializer serializer) {
- serialize(serializer, "dx", "dy").add("type", CLASS_NAME);
+ serialize(serializer, "dx", "dy").addType(CLASS_NAME);
}
}
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/NamedVariable.java b/core/java/com/android/internal/widget/remotecompose/core/operations/NamedVariable.java
index 96628fd51225..9a880858b5a5 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/NamedVariable.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/NamedVariable.java
@@ -25,7 +25,6 @@ import com.android.internal.widget.remotecompose.core.Operations;
import com.android.internal.widget.remotecompose.core.RemoteContext;
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;
@@ -43,6 +42,8 @@ public class NamedVariable extends Operation implements Serializable {
public static final int FLOAT_TYPE = 1;
public static final int STRING_TYPE = 0;
public static final int IMAGE_TYPE = 3;
+ public static final int INT_TYPE = 4;
+ public static final int LONG_TYPE = 5;
public NamedVariable(int varId, int varType, @NonNull String name) {
this.mVarId = varId;
@@ -122,7 +123,7 @@ public class NamedVariable extends Operation implements Serializable {
public static void documentation(@NonNull DocumentationBuilder doc) {
doc.operation("Data Operations", OP_CODE, CLASS_NAME)
.description("Add a string name for an ID")
- .field(DocumentedOperation.INT, "varId", "id to label")
+ .field(INT, "varId", "id to label")
.field(INT, "varType", "The type of variable")
.field(UTF8, "name", "String");
}
@@ -141,7 +142,7 @@ public class NamedVariable extends Operation implements Serializable {
@Override
public void serialize(MapSerializer serializer) {
serializer
- .add("type", CLASS_NAME)
+ .addType(CLASS_NAME)
.add("varId", mVarId)
.add("varName", mVarName)
.add("varType", typeToString());
@@ -157,6 +158,8 @@ public class NamedVariable extends Operation implements Serializable {
return "STRING_TYPE";
case IMAGE_TYPE:
return "IMAGE_TYPE";
+ case INT_TYPE:
+ return "INT_TYPE";
default:
return "INVALID_TYPE";
}
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/PaintData.java b/core/java/com/android/internal/widget/remotecompose/core/operations/PaintData.java
index 8389aa707ee6..70197c6d085d 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/PaintData.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/PaintData.java
@@ -131,6 +131,6 @@ public class PaintData extends PaintOperation implements VariableSupport, Serial
@Override
public void serialize(MapSerializer serializer) {
- serializer.add("type", CLASS_NAME).add("paintBundle", mPaintData);
+ serializer.addType(CLASS_NAME).add("paintBundle", mPaintData);
}
}
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/ParticlesLoop.java b/core/java/com/android/internal/widget/remotecompose/core/operations/ParticlesLoop.java
index 8d19c94df604..f9fdfdf09e35 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/ParticlesLoop.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/ParticlesLoop.java
@@ -33,6 +33,7 @@ import com.android.internal.widget.remotecompose.core.documentation.DocumentedOp
import com.android.internal.widget.remotecompose.core.operations.layout.Container;
import com.android.internal.widget.remotecompose.core.operations.utilities.AnimatedFloatExpression;
import com.android.internal.widget.remotecompose.core.operations.utilities.NanMap;
+import com.android.internal.widget.remotecompose.core.serialize.MapSerializer;
import java.util.ArrayList;
import java.util.List;
@@ -292,4 +293,9 @@ public class ParticlesLoop extends PaintOperation implements VariableSupport, Co
}
}
}
+
+ @Override
+ public void serialize(MapSerializer serializer) {
+ serializer.addType(CLASS_NAME).add("id", mId);
+ }
}
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/PathAppend.java b/core/java/com/android/internal/widget/remotecompose/core/operations/PathAppend.java
index 8f353ce4a26b..8a747e134897 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/PathAppend.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/PathAppend.java
@@ -258,9 +258,6 @@ public class PathAppend extends PaintOperation implements VariableSupport, Seria
@Override
public void serialize(MapSerializer serializer) {
- serializer
- .add("type", CLASS_NAME)
- .add("id", mInstanceId)
- .add("path", pathString(mFloatPath));
+ serializer.addType(CLASS_NAME).add("id", mInstanceId).add("path", pathString(mFloatPath));
}
}
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/PathCreate.java b/core/java/com/android/internal/widget/remotecompose/core/operations/PathCreate.java
index 7aa3390b53ee..78e3b9eac110 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/PathCreate.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/PathCreate.java
@@ -242,9 +242,6 @@ public class PathCreate extends PaintOperation implements VariableSupport, Seria
@Override
public void serialize(MapSerializer serializer) {
- serializer
- .add("type", CLASS_NAME)
- .add("id", mInstanceId)
- .add("path", pathString(mFloatPath));
+ serializer.addType(CLASS_NAME).add("id", mInstanceId).add("path", pathString(mFloatPath));
}
}
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/PathData.java b/core/java/com/android/internal/widget/remotecompose/core/operations/PathData.java
index 9564f15209c7..cedc4f3b0e45 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/PathData.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/PathData.java
@@ -243,9 +243,6 @@ public class PathData extends Operation implements VariableSupport, Serializable
@Override
public void serialize(MapSerializer serializer) {
- serializer
- .add("type", CLASS_NAME)
- .add("id", mInstanceId)
- .add("path", pathString(mFloatPath));
+ serializer.addType(CLASS_NAME).add("id", mInstanceId).add("path", pathString(mFloatPath));
}
}
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/PathTween.java b/core/java/com/android/internal/widget/remotecompose/core/operations/PathTween.java
index c5add57d4dd0..09b29e8c5e5c 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/PathTween.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/PathTween.java
@@ -161,7 +161,7 @@ public class PathTween extends PaintOperation implements VariableSupport, Serial
@Override
public void serialize(MapSerializer serializer) {
serializer
- .add("type", CLASS_NAME)
+ .addType(CLASS_NAME)
.add("outId", mOutId)
.add("pathId1", mPathId1)
.add("pathId2", mPathId2)
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/RootContentDescription.java b/core/java/com/android/internal/widget/remotecompose/core/operations/RootContentDescription.java
index a6a8a810d2ad..214d240a39fd 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/RootContentDescription.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/RootContentDescription.java
@@ -133,6 +133,6 @@ public class RootContentDescription extends Operation
@Override
public void serialize(MapSerializer serializer) {
- serializer.add("type", CLASS_NAME).add("contentDescriptionId", mContentDescription);
+ serializer.addType(CLASS_NAME).add("contentDescriptionId", mContentDescription);
}
}
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/ShaderData.java b/core/java/com/android/internal/widget/remotecompose/core/operations/ShaderData.java
index 5f6162b68e9e..013b6f64b91a 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/ShaderData.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/ShaderData.java
@@ -390,7 +390,7 @@ public class ShaderData extends Operation implements VariableSupport, Serializab
@Override
public void serialize(MapSerializer serializer) {
serializer
- .add("type", CLASS_NAME)
+ .addType(CLASS_NAME)
.add("shaderTextId", mShaderTextId)
.add("shaderID", mShaderID)
.add("uniformRawFloatMap", mUniformRawFloatMap)
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/TextAttribute.java b/core/java/com/android/internal/widget/remotecompose/core/operations/TextAttribute.java
index 3e72995de9db..36f25c6ce944 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/TextAttribute.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/TextAttribute.java
@@ -173,7 +173,7 @@ public class TextAttribute extends PaintOperation implements Serializable {
@Override
public void serialize(MapSerializer serializer) {
serializer
- .add("type", CLASS_NAME)
+ .addType(CLASS_NAME)
.add("id", mId)
.add("textId", mTextId)
.add("measureType", typeToString());
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 419e6d074479..67773d1d6187 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
@@ -136,6 +136,6 @@ public class TextData extends Operation implements SerializableToString, Seriali
@Override
public void serialize(MapSerializer serializer) {
- serializer.add("type", CLASS_NAME).add("textId", mTextId).add("text", mText);
+ serializer.addType(CLASS_NAME).add("textId", mTextId).add("text", mText);
}
}
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/TextFromFloat.java b/core/java/com/android/internal/widget/remotecompose/core/operations/TextFromFloat.java
index 6b2f49be76f0..f22369f51ce9 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/TextFromFloat.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/TextFromFloat.java
@@ -215,7 +215,7 @@ public class TextFromFloat extends Operation implements VariableSupport, Seriali
@Override
public void serialize(MapSerializer serializer) {
serializer
- .add("type", CLASS_NAME)
+ .addType(CLASS_NAME)
.add("textId", mTextId)
.add("value", mValue, mOutValue)
.add("digitsBefore", mDigitsBefore)
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/TextLookup.java b/core/java/com/android/internal/widget/remotecompose/core/operations/TextLookup.java
index e8865c26db12..fa44bf165cd7 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/TextLookup.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/TextLookup.java
@@ -156,7 +156,7 @@ public class TextLookup extends Operation implements VariableSupport, Serializab
@Override
public void serialize(MapSerializer serializer) {
serializer
- .add("type", CLASS_NAME)
+ .addType(CLASS_NAME)
.add("textId", mTextId)
.add("dataSetId", mDataSetId)
.add("indexId", mIndex, mOutIndex);
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/TextLookupInt.java b/core/java/com/android/internal/widget/remotecompose/core/operations/TextLookupInt.java
index de2025569d46..5ec3290c445d 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/TextLookupInt.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/TextLookupInt.java
@@ -149,7 +149,7 @@ public class TextLookupInt extends Operation implements VariableSupport, Seriali
@Override
public void serialize(MapSerializer serializer) {
serializer
- .add("type", CLASS_NAME)
+ .addType(CLASS_NAME)
.add("textId", mTextId)
.add("dataSetId", mDataSetId)
.add("indexId", mIndex, mOutIndex);
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/TextMeasure.java b/core/java/com/android/internal/widget/remotecompose/core/operations/TextMeasure.java
index 58cd68e2a5db..3559d1dbe53b 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/TextMeasure.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/TextMeasure.java
@@ -27,6 +27,7 @@ import com.android.internal.widget.remotecompose.core.PaintContext;
import com.android.internal.widget.remotecompose.core.PaintOperation;
import com.android.internal.widget.remotecompose.core.WireBuffer;
import com.android.internal.widget.remotecompose.core.documentation.DocumentationBuilder;
+import com.android.internal.widget.remotecompose.core.serialize.MapSerializer;
import java.util.List;
@@ -161,4 +162,33 @@ public class TextMeasure extends PaintOperation {
break;
}
}
+
+ @Override
+ public void serialize(MapSerializer serializer) {
+ serializer
+ .addType(CLASS_NAME)
+ .add("id", mId)
+ .add("textId", mTextId)
+ .add("measureType", typeToString());
+ }
+
+ private String typeToString() {
+ int val = mType & 255;
+ switch (val) {
+ case MEASURE_WIDTH:
+ return "MEASURE_WIDTH";
+ case MEASURE_HEIGHT:
+ return "MEASURE_HEIGHT";
+ case MEASURE_LEFT:
+ return "MEASURE_LEFT";
+ case MEASURE_TOP:
+ return "MEASURE_TOP";
+ case MEASURE_RIGHT:
+ return "MEASURE_RIGHT";
+ case MEASURE_BOTTOM:
+ return "MEASURE_BOTTOM";
+ default:
+ return "INVALID_TYPE";
+ }
+ }
}
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 262916dd9d0c..1239b5648446 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
@@ -132,7 +132,7 @@ public class TextMerge extends Operation implements Serializable {
@Override
public void serialize(MapSerializer serializer) {
serializer
- .add("type", CLASS_NAME)
+ .addType(CLASS_NAME)
.add("id", mTextId)
.add("leftId", mSrcId1)
.add("rightId", mSrcId2);
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 afb84b56d7b0..fd9a2bf5e435 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
@@ -27,12 +27,14 @@ import com.android.internal.widget.remotecompose.core.PaintOperation;
import com.android.internal.widget.remotecompose.core.RemoteContext;
import com.android.internal.widget.remotecompose.core.WireBuffer;
import com.android.internal.widget.remotecompose.core.documentation.DocumentationBuilder;
+import com.android.internal.widget.remotecompose.core.serialize.MapSerializer;
import com.android.internal.widget.remotecompose.core.types.LongConstant;
import java.time.Instant;
import java.time.LocalDateTime;
import java.time.ZoneOffset;
import java.util.Arrays;
+import java.util.Collections;
import java.util.List;
/** Operation to perform time related calculation */
@@ -292,4 +294,48 @@ public class TimeAttribute extends PaintOperation {
break;
}
}
+
+ @Override
+ public void serialize(MapSerializer serializer) {
+ serializer
+ .addType(CLASS_NAME)
+ .add("id", mId)
+ .add("timeId", mTimeId)
+ .addType(getTypeString())
+ .add("args", Collections.singletonList(mArgs));
+ }
+
+ private String getTypeString() {
+ int val = mType & 255;
+ switch (val) {
+ case TIME_FROM_NOW_SEC:
+ return "TIME_FROM_NOW_SEC";
+ case TIME_FROM_NOW_MIN:
+ return "TIME_FROM_NOW_MIN";
+ case TIME_FROM_NOW_HR:
+ return "TIME_FROM_NOW_HR";
+ case TIME_FROM_ARG_SEC:
+ return "TIME_FROM_ARG_SEC";
+ case TIME_FROM_ARG_MIN:
+ return "TIME_FROM_ARG_MIN";
+ case TIME_FROM_ARG_HR:
+ return "TIME_FROM_ARG_HR";
+ case TIME_IN_SEC:
+ return "TIME_IN_SEC";
+ case TIME_IN_MIN:
+ return "TIME_IN_MIN";
+ case TIME_IN_HR:
+ return "TIME_IN_HR";
+ case TIME_DAY_OF_MONTH:
+ return "TIME_DAY_OF_MONTH";
+ case TIME_MONTH_VALUE:
+ return "TIME_MONTH_VALUE";
+ case TIME_DAY_OF_WEEK:
+ return "TIME_DAY_OF_WEEK";
+ case TIME_YEAR:
+ return "TIME_YEAR";
+ default:
+ return "INVALID_TIME_TYPE";
+ }
+ }
}
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/TouchExpression.java b/core/java/com/android/internal/widget/remotecompose/core/operations/TouchExpression.java
index 2591a4c39778..f24672922367 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/TouchExpression.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/TouchExpression.java
@@ -716,9 +716,9 @@ public class TouchExpression extends Operation
@Override
public void serialize(MapSerializer serializer) {
serializer
- .add("type", CLASS_NAME)
+ .addType(CLASS_NAME)
.add("id", mId)
- .add("mDefValue", mDefValue, mOutDefValue)
+ .add("defValue", mDefValue, mOutDefValue)
.add("min", mMin, mOutMin)
.add("max", mMax, mOutMax)
.add("mode", mMode)
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/AnimatableValue.java b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/AnimatableValue.java
index 9dc2a49d26ef..b98a017addb3 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/AnimatableValue.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/AnimatableValue.java
@@ -129,6 +129,6 @@ public class AnimatableValue implements Serializable {
@Override
public void serialize(MapSerializer serializer) {
- serializer.add("type", "AnimatableValue").add("id", mId);
+ serializer.addType("AnimatableValue").add("id", mId);
}
}
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 3e7f1d304315..25a10ab7dbeb 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
@@ -156,7 +156,7 @@ public class CanvasOperations extends PaintOperation
@Override
public void serialize(MapSerializer serializer) {
- serializer.add("type", CLASS_NAME).add("list", mList);
+ serializer.addType(CLASS_NAME).add("list", mList);
}
/**
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/ClickModifierOperation.java b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/ClickModifierOperation.java
index 8b13c13a3333..7ee14903ced5 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/ClickModifierOperation.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/ClickModifierOperation.java
@@ -253,6 +253,6 @@ public class ClickModifierOperation extends PaintOperation
@Override
public void serialize(MapSerializer serializer) {
- serializer.addTags(SerializeTags.MODIFIER).add("type", "ClickModifierOperation");
+ serializer.addTags(SerializeTags.MODIFIER).addType("ClickModifierOperation");
}
}
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 c73643682b55..b30dade828a7 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
@@ -57,8 +57,8 @@ public class Component extends PaintOperation
protected float mHeight;
@Nullable protected Component mParent;
protected int mAnimationId = -1;
- @NonNull public Visibility mVisibility = Visibility.VISIBLE;
- @NonNull public Visibility mScheduledVisibility = Visibility.VISIBLE;
+ public int mVisibility = Visibility.VISIBLE;
+ public int mScheduledVisibility = Visibility.VISIBLE;
@NonNull public ArrayList<Operation> mList = new ArrayList<>();
public PaintOperation mPreTranslate; // todo, can we initialize this here and make it NonNull?
public boolean mNeedsMeasure = true;
@@ -288,22 +288,42 @@ public class Component extends PaintOperation
}
/**
- * Returns the intrinsic width of the layout
+ * Returns the min intrinsic width of the layout
*
* @param context
* @return the width in pixels
*/
- public float intrinsicWidth(@Nullable RemoteContext context) {
+ public float minIntrinsicWidth(@Nullable RemoteContext context) {
return getWidth();
}
/**
- * Returns the intrinsic height of the layout
+ * Returns the max intrinsic width of the layout
+ *
+ * @param context
+ * @return the width in pixels
+ */
+ public float maxIntrinsicWidth(@Nullable RemoteContext context) {
+ return getWidth();
+ }
+
+ /**
+ * Returns the min intrinsic height of the layout
+ *
+ * @param context
+ * @return the height in pixels
+ */
+ public float minIntrinsicHeight(@Nullable RemoteContext context) {
+ return getHeight();
+ }
+
+ /**
+ * Returns the max intrinsic height of the layout
*
* @param context
* @return the height in pixels
*/
- public float intrinsicHeight(@Nullable RemoteContext context) {
+ public float maxIntrinsicHeight(@Nullable RemoteContext context) {
return getHeight();
}
@@ -338,10 +358,119 @@ public class Component extends PaintOperation
// Nothing here
}
- public enum Visibility {
- GONE,
- VISIBLE,
- INVISIBLE
+ public static class Visibility {
+
+ public static final int GONE = 0;
+ public static final int VISIBLE = 1;
+ public static final int INVISIBLE = 2;
+ public static final int OVERRIDE_GONE = 16;
+ public static final int OVERRIDE_VISIBLE = 32;
+ public static final int OVERRIDE_INVISIBLE = 64;
+ public static final int CLEAR_OVERRIDE = 128;
+
+ /**
+ * Returns a string representation of the field
+ *
+ * @param value
+ * @return
+ */
+ public static String toString(int value) {
+ switch (value) {
+ case GONE:
+ return "GONE";
+ case VISIBLE:
+ return "VISIBLE";
+ case INVISIBLE:
+ return "INVISIBLE";
+ }
+ if ((value >> 4) > 0) {
+ if ((value & OVERRIDE_GONE) == OVERRIDE_GONE) {
+ return "OVERRIDE_GONE";
+ }
+ if ((value & OVERRIDE_VISIBLE) == OVERRIDE_VISIBLE) {
+ return "OVERRIDE_VISIBLE";
+ }
+ if ((value & OVERRIDE_INVISIBLE) == OVERRIDE_INVISIBLE) {
+ return "OVERRIDE_INVISIBLE";
+ }
+ }
+ return "" + value;
+ }
+
+ /**
+ * Returns true if gone
+ *
+ * @param value
+ * @return
+ */
+ public static boolean isGone(int value) {
+ if ((value >> 4) > 0) {
+ return (value & OVERRIDE_GONE) == OVERRIDE_GONE;
+ }
+ return value == GONE;
+ }
+
+ /**
+ * Returns true if visible
+ *
+ * @param value
+ * @return
+ */
+ public static boolean isVisible(int value) {
+ if ((value >> 4) > 0) {
+ return (value & OVERRIDE_VISIBLE) == OVERRIDE_VISIBLE;
+ }
+ return value == VISIBLE;
+ }
+
+ /**
+ * Returns true if invisible
+ *
+ * @param value
+ * @return
+ */
+ public static boolean isInvisible(int value) {
+ if ((value >> 4) > 0) {
+ return (value & OVERRIDE_INVISIBLE) == OVERRIDE_INVISIBLE;
+ }
+ return value == INVISIBLE;
+ }
+
+ /**
+ * Returns true if the field has an override
+ *
+ * @param value
+ * @return
+ */
+ public static boolean hasOverride(int value) {
+ return (value >> 4) > 0;
+ }
+
+ /**
+ * Clear the override values
+ *
+ * @param value
+ * @return
+ */
+ public static int clearOverride(int value) {
+ return value & 15;
+ }
+
+ /**
+ * Add an override value
+ *
+ * @param value
+ * @param visibility
+ * @return
+ */
+ public static int add(int value, int visibility) {
+ int v = value & 15;
+ v += visibility;
+ if ((v & CLEAR_OVERRIDE) == CLEAR_OVERRIDE) {
+ v = v & 15;
+ }
+ return v;
+ }
}
/**
@@ -350,13 +479,28 @@ public class Component extends PaintOperation
* @return
*/
public boolean isVisible() {
- if (mVisibility != Visibility.VISIBLE || mParent == null) {
- return mVisibility == Visibility.VISIBLE;
- }
- if (mParent != null) { // TODO: this is always true -- bbade@
- return mParent.isVisible();
+ if (mParent == null || !Visibility.isVisible(mVisibility)) {
+ return Visibility.isVisible(mVisibility);
}
- return true;
+ return mParent.isVisible();
+ }
+
+ /**
+ * Returns true if the component is gone
+ *
+ * @return
+ */
+ public boolean isGone() {
+ return Visibility.isGone(mVisibility);
+ }
+
+ /**
+ * Returns true if the component is invisible
+ *
+ * @return
+ */
+ public boolean isInvisible() {
+ return Visibility.isInvisible(mVisibility);
}
/**
@@ -364,7 +508,7 @@ public class Component extends PaintOperation
*
* @param visibility can be VISIBLE, INVISIBLE or GONE
*/
- public void setVisibility(@NonNull Visibility visibility) {
+ public void setVisibility(int visibility) {
if (visibility != mVisibility || visibility != mScheduledVisibility) {
mScheduledVisibility = visibility;
invalidateMeasure();
@@ -705,7 +849,7 @@ public class Component extends PaintOperation
+ "] "
+ textContent()
+ " Visibility ("
- + mVisibility
+ + Visibility.toString(mVisibility)
+ ") ";
}
@@ -732,7 +876,7 @@ public class Component extends PaintOperation
+ ", "
+ mHeight
+ "] "
- + mVisibility;
+ + Visibility.toString(mVisibility);
// + " [" + mNeedsMeasure + ", " + mNeedsRepaint + "]"
serializer.append(indent, content);
}
@@ -966,7 +1110,7 @@ public class Component extends PaintOperation
if (applyAnimationAsNeeded(context)) {
return;
}
- if (mVisibility == Visibility.GONE || mVisibility == Visibility.INVISIBLE) {
+ if (isGone() || isInvisible()) {
return;
}
paintingComponent(context);
@@ -1071,13 +1215,13 @@ public class Component extends PaintOperation
@Override
public void serialize(MapSerializer serializer) {
serializer.addTags(SerializeTags.COMPONENT);
- serializer.add("type", getSerializedName());
+ serializer.addType(getSerializedName());
serializer.add("id", mComponentId);
serializer.add("x", mX);
serializer.add("y", mY);
serializer.add("width", mWidth);
serializer.add("height", mHeight);
- serializer.add("visibility", mVisibility);
+ serializer.add("visibility", Visibility.toString(mVisibility));
serializer.add("list", mList);
}
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 e277d49325be..0e629c5d2448 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
@@ -27,6 +27,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.documentation.DocumentedOperation;
import com.android.internal.widget.remotecompose.core.operations.Utils;
+import com.android.internal.widget.remotecompose.core.serialize.MapSerializer;
import java.util.ArrayList;
import java.util.List;
@@ -228,4 +229,12 @@ public class ImpulseOperation extends PaintOperation implements VariableSupport,
public void setProcess(ImpulseProcess impulseProcess) {
mProcess = impulseProcess;
}
+
+ @Override
+ public void serialize(MapSerializer serializer) {
+ serializer
+ .addType(CLASS_NAME)
+ .add("duration", mDuration, mOutDuration)
+ .add("startAt", mStartAt, mOutStartAt);
+ }
}
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/ImpulseProcess.java b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/ImpulseProcess.java
index 8c9dd76c9ed5..83d4d38be66e 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/ImpulseProcess.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/ImpulseProcess.java
@@ -157,6 +157,6 @@ public class ImpulseProcess extends PaintOperation
@Override
public void serialize(MapSerializer serializer) {
- serializer.add("type", CLASS_NAME).add("list", mList);
+ serializer.addType(CLASS_NAME).add("list", mList);
}
}
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 2b63cf246555..dda328f57a3b 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
@@ -206,12 +206,12 @@ public class LoopOperation extends PaintOperation
@Override
public void serialize(MapSerializer serializer) {
serializer
- .add("type", CLASS_NAME)
+ .addType(CLASS_NAME)
.add("indexVariableId", mIndexVariableId)
.add("until", mUntil, mUntilOut)
.add("from", mFrom, mFromOut)
.add("step", mStep, mStepOut)
- .add("mUntilOut", mUntilOut)
+ .add("untilOut", mUntilOut)
.add("list", mList);
}
}
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/RootLayoutComponent.java b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/RootLayoutComponent.java
index f2503b26513e..77d3dae74558 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/RootLayoutComponent.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/RootLayoutComponent.java
@@ -77,7 +77,7 @@ public class RootLayoutComponent extends Component {
+ " x "
+ mHeight
+ ") "
- + mVisibility;
+ + Visibility.toString(mVisibility);
}
@Override
@@ -97,7 +97,7 @@ public class RootLayoutComponent extends Component {
+ ", "
+ mHeight
+ "] "
- + mVisibility);
+ + Visibility.toString(mVisibility));
}
/**
@@ -282,6 +282,6 @@ public class RootLayoutComponent extends Component {
public void serialize(MapSerializer serializer) {
super.serialize(serializer);
serializer.addTags(SerializeTags.COMPONENT);
- serializer.add("type", "RootLayoutComponent");
+ serializer.addType("RootLayoutComponent");
}
}
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/TouchCancelModifierOperation.java b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/TouchCancelModifierOperation.java
index 62b1b6cf9615..283bc7a45f85 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/TouchCancelModifierOperation.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/TouchCancelModifierOperation.java
@@ -129,6 +129,6 @@ public class TouchCancelModifierOperation extends ListActionsOperation implement
@Override
public void serialize(MapSerializer serializer) {
super.serialize(serializer);
- serializer.add("type", "TouchCancelModifierOperation");
+ serializer.addType("TouchCancelModifierOperation");
}
}
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/TouchDownModifierOperation.java b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/TouchDownModifierOperation.java
index 5289fda2c74b..b010c148d122 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/TouchDownModifierOperation.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/TouchDownModifierOperation.java
@@ -131,6 +131,6 @@ public class TouchDownModifierOperation extends ListActionsOperation implements
@Override
public void serialize(MapSerializer serializer) {
super.serialize(serializer);
- serializer.add("type", "TouchDownModifierOperation");
+ serializer.addType("TouchDownModifierOperation");
}
}
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/TouchUpModifierOperation.java b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/TouchUpModifierOperation.java
index 576c5e9281a4..bc5c10b86c96 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/TouchUpModifierOperation.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/TouchUpModifierOperation.java
@@ -129,6 +129,6 @@ public class TouchUpModifierOperation extends ListActionsOperation implements To
@Override
public void serialize(MapSerializer serializer) {
super.serialize(serializer);
- serializer.add("type", "TouchUpModifierOperation");
+ serializer.addType("TouchUpModifierOperation");
}
}
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/animation/AnimateMeasure.java b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/animation/AnimateMeasure.java
index e5cd485967e8..1a60451ddd3b 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/animation/AnimateMeasure.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/animation/AnimateMeasure.java
@@ -143,7 +143,7 @@ public class AnimateMeasure {
*/
public void paint(@NonNull PaintContext context) {
if (mOriginal.getVisibility() != mTarget.getVisibility()) {
- if (mTarget.getVisibility() == Component.Visibility.GONE) {
+ if (mTarget.isGone()) {
switch (mExitAnimation) {
case PARTICLE:
// particleAnimation(context, component, original, target, vp)
@@ -229,8 +229,7 @@ public class AnimateMeasure {
mParticleAnimation.animate(context, mComponent, mOriginal, mTarget, mVp);
break;
}
- } else if (mOriginal.getVisibility() == Component.Visibility.GONE
- && mTarget.getVisibility() == Component.Visibility.VISIBLE) {
+ } else if (mOriginal.isGone() && mTarget.isVisible()) {
switch (mEnterAnimation) {
case ROTATE:
float px = mTarget.getX() + mTarget.getW() / 2f;
@@ -323,7 +322,7 @@ public class AnimateMeasure {
} else {
mComponent.paintingComponent(context);
}
- } else if (mTarget.getVisibility() == Component.Visibility.VISIBLE) {
+ } else if (mTarget.isVisible()) {
mComponent.paintingComponent(context);
}
@@ -360,7 +359,7 @@ public class AnimateMeasure {
public float getVisibility() {
if (mOriginal.getVisibility() == mTarget.getVisibility()) {
return 1f;
- } else if (mTarget.getVisibility() == Component.Visibility.VISIBLE) {
+ } else if (mTarget.isVisible()) {
return mVp;
} else {
return 1 - mVp;
@@ -382,7 +381,7 @@ public class AnimateMeasure {
float targetY = mTarget.getY();
float targetW = mTarget.getW();
float targetH = mTarget.getH();
- Component.Visibility targetVisibility = mTarget.getVisibility();
+ int targetVisibility = mTarget.getVisibility();
if (targetX != measure.getX()
|| targetY != measure.getY()
|| targetW != measure.getW()
@@ -393,7 +392,13 @@ public class AnimateMeasure {
mTarget.setW(measure.getW());
mTarget.setH(measure.getH());
mTarget.setVisibility(measure.getVisibility());
- mStartTime = currentTime;
+ // We shouldn't reset the leftover animation time here
+ // 1/ if we are eg fading out a component, and an updateTarget comes on, we don't want
+ // to restart the full animation time
+ // 2/ if no visibility change but quick updates come in (eg live resize) it seems
+ // better as well to not restart the animation time and only allows the original
+ // time to wrap up
+ // mStartTime = currentTime;
}
}
}
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 91348f5810a1..c87bbdc9809d 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
@@ -129,7 +129,7 @@ public class AnimationSpec extends Operation implements ModifierOperation {
@Override
public void serialize(MapSerializer serializer) {
serializer
- .add("type", "AnimationSpec")
+ .addType("AnimationSpec")
.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/BoxLayout.java b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/managers/BoxLayout.java
index 35d639e65385..6ee18bbbd7e0 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/managers/BoxLayout.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/managers/BoxLayout.java
@@ -115,8 +115,10 @@ public class BoxLayout extends LayoutManager {
for (Component c : mChildrenComponents) {
c.measure(context, 0f, maxWidth, 0f, maxHeight, measure);
ComponentMeasure m = measure.get(c);
- size.setWidth(Math.max(size.getWidth(), m.getW()));
- size.setHeight(Math.max(size.getHeight(), m.getH()));
+ if (!m.isGone()) {
+ size.setWidth(Math.max(size.getWidth(), m.getW()));
+ size.setHeight(Math.max(size.getHeight(), m.getH()));
+ }
}
// add padding
size.setWidth(Math.max(size.getWidth(), computeModifierDefinedWidth(context.getContext())));
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 508b685b5407..f9111dffe2c4 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
@@ -154,7 +154,7 @@ public class CanvasLayout extends BoxLayout {
@Override
public void serialize(MapSerializer serializer) {
super.serialize(serializer);
- serializer.add("type", getSerializedName());
+ serializer.addType(getSerializedName());
serializer.add("horizontalPositioning", mHorizontalPositioning);
}
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/managers/CollapsibleColumnLayout.java b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/managers/CollapsibleColumnLayout.java
index afc41b1873ef..b0089525af5a 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/managers/CollapsibleColumnLayout.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/managers/CollapsibleColumnLayout.java
@@ -21,6 +21,7 @@ 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.WireBuffer;
import com.android.internal.widget.remotecompose.core.operations.layout.Component;
import com.android.internal.widget.remotecompose.core.operations.layout.measure.ComponentMeasure;
@@ -134,6 +135,24 @@ public class CollapsibleColumnLayout extends ColumnLayout {
}
@Override
+ public float minIntrinsicHeight(@NonNull RemoteContext context) {
+ float height = computeModifierDefinedHeight(context);
+ if (!mChildrenComponents.isEmpty()) {
+ height += mChildrenComponents.get(0).minIntrinsicHeight(context);
+ }
+ return height;
+ }
+
+ @Override
+ public float minIntrinsicWidth(@NonNull RemoteContext context) {
+ float width = computeModifierDefinedWidth(context);
+ if (!mChildrenComponents.isEmpty()) {
+ width += mChildrenComponents.get(0).minIntrinsicWidth(context);
+ }
+ return width;
+ }
+
+ @Override
protected boolean hasVerticalIntrinsicDimension() {
return true;
}
@@ -147,29 +166,54 @@ public class CollapsibleColumnLayout extends ColumnLayout {
boolean verticalWrap,
@NonNull MeasurePass measure,
@NonNull Size size) {
- super.computeWrapSize(
- context, maxWidth, Float.MAX_VALUE, horizontalWrap, verticalWrap, measure, size);
- }
+ int visibleChildren = 0;
+ ComponentMeasure self = measure.get(this);
+ self.addVisibilityOverride(Visibility.OVERRIDE_VISIBLE);
+ float currentMaxHeight = maxHeight;
+ for (Component c : mChildrenComponents) {
+ if (c instanceof CollapsibleColumnLayout) {
+ c.measure(context, 0f, maxWidth, 0f, currentMaxHeight, measure);
+ } else {
+ c.measure(context, 0f, maxWidth, 0f, Float.MAX_VALUE, measure);
+ }
+ ComponentMeasure m = measure.get(c);
+ if (!m.isGone()) {
+ size.setWidth(Math.max(size.getWidth(), m.getW()));
+ size.setHeight(size.getHeight() + m.getH());
+ visibleChildren++;
+ currentMaxHeight -= m.getH();
+ }
+ }
+ if (!mChildrenComponents.isEmpty()) {
+ size.setHeight(size.getHeight() + (mSpacedBy * (visibleChildren - 1)));
+ }
- @Override
- public boolean applyVisibility(
- float selfWidth, float selfHeight, @NonNull MeasurePass measure) {
float childrenWidth = 0f;
float childrenHeight = 0f;
- boolean changedVisibility = false;
+
+ boolean overflow = false;
for (Component child : mChildrenComponents) {
ComponentMeasure childMeasure = measure.get(child);
- if (childMeasure.getVisibility() == Visibility.GONE) {
+ if (overflow || childMeasure.isGone()) {
+ childMeasure.addVisibilityOverride(Visibility.OVERRIDE_GONE);
continue;
}
- if (childrenHeight + childMeasure.getH() > selfHeight) {
- childMeasure.setVisibility(Visibility.GONE);
- changedVisibility = true;
+ float childHeight = childMeasure.getH();
+ boolean childDoesNotFits = childrenHeight + childHeight > maxHeight;
+ if (childDoesNotFits) {
+ childMeasure.addVisibilityOverride(Visibility.OVERRIDE_GONE);
+ overflow = true;
} else {
- childrenHeight += childMeasure.getH();
+ childrenHeight += childHeight;
childrenWidth = Math.max(childrenWidth, childMeasure.getW());
+ visibleChildren++;
}
}
- return changedVisibility;
+ if (verticalWrap) {
+ size.setHeight(Math.min(maxHeight, childrenHeight));
+ }
+ if (visibleChildren == 0 || size.getHeight() <= 0f) {
+ self.addVisibilityOverride(Visibility.OVERRIDE_GONE);
+ }
}
}
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/managers/CollapsibleRowLayout.java b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/managers/CollapsibleRowLayout.java
index 0e7eb8676f46..05f332960c16 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/managers/CollapsibleRowLayout.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/managers/CollapsibleRowLayout.java
@@ -21,6 +21,7 @@ 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.WireBuffer;
import com.android.internal.widget.remotecompose.core.operations.layout.Component;
import com.android.internal.widget.remotecompose.core.operations.layout.measure.ComponentMeasure;
@@ -139,6 +140,24 @@ public class CollapsibleRowLayout extends RowLayout {
}
@Override
+ public float minIntrinsicWidth(@NonNull RemoteContext context) {
+ float width = computeModifierDefinedWidth(context);
+ if (!mChildrenComponents.isEmpty()) {
+ width += mChildrenComponents.get(0).minIntrinsicWidth(context);
+ }
+ return width;
+ }
+
+ @Override
+ public float minIntrinsicHeight(@NonNull RemoteContext context) {
+ float height = computeModifierDefinedHeight(context);
+ if (!mChildrenComponents.isEmpty()) {
+ height += mChildrenComponents.get(0).minIntrinsicHeight(context);
+ }
+ return height;
+ }
+
+ @Override
public void computeWrapSize(
@NonNull PaintContext context,
float maxWidth,
@@ -157,19 +176,35 @@ public class CollapsibleRowLayout extends RowLayout {
float childrenWidth = 0f;
float childrenHeight = 0f;
boolean changedVisibility = false;
+ int visibleChildren = 0;
+ ComponentMeasure self = measure.get(this);
+ self.clearVisibilityOverride();
+ if (selfWidth <= 0 || selfHeight <= 0) {
+ self.addVisibilityOverride(Visibility.OVERRIDE_GONE);
+ return true;
+ }
for (Component child : mChildrenComponents) {
ComponentMeasure childMeasure = measure.get(child);
- if (childMeasure.getVisibility() == Visibility.GONE) {
+ int visibility = childMeasure.getVisibility();
+ childMeasure.clearVisibilityOverride();
+ if (!childMeasure.isVisible()) {
continue;
}
- if (childrenWidth + childMeasure.getW() > selfWidth) {
- childMeasure.setVisibility(Visibility.GONE);
- changedVisibility = true;
+ if (childrenWidth + childMeasure.getW() > selfWidth
+ && childrenHeight + childMeasure.getH() > selfHeight) {
+ childMeasure.addVisibilityOverride(Visibility.OVERRIDE_GONE);
+ if (visibility != childMeasure.getVisibility()) {
+ changedVisibility = true;
+ }
} else {
childrenWidth += childMeasure.getW();
childrenHeight = Math.max(childrenHeight, childMeasure.getH());
+ visibleChildren++;
}
}
+ if (visibleChildren == 0) {
+ self.addVisibilityOverride(Visibility.OVERRIDE_GONE);
+ }
return changedVisibility;
}
}
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/managers/ColumnLayout.java b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/managers/ColumnLayout.java
index 47a55b6ed82a..cda90c2d3b0b 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/managers/ColumnLayout.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/managers/ColumnLayout.java
@@ -138,7 +138,7 @@ public class ColumnLayout extends LayoutManager {
for (Component c : mChildrenComponents) {
c.measure(context, 0f, maxWidth, 0f, currentMaxHeight, measure);
ComponentMeasure m = measure.get(c);
- if (m.getVisibility() != Visibility.GONE) {
+ if (!m.isGone()) {
size.setWidth(Math.max(size.getWidth(), m.getW()));
size.setHeight(size.getHeight() + m.getH());
visibleChildrens++;
@@ -164,7 +164,7 @@ public class ColumnLayout extends LayoutManager {
for (Component child : mChildrenComponents) {
child.measure(context, minWidth, maxWidth, minHeight, mh, measure);
ComponentMeasure m = measure.get(child);
- if (m.getVisibility() != Visibility.GONE) {
+ if (!m.isGone()) {
mh -= m.getH();
}
}
@@ -172,11 +172,11 @@ public class ColumnLayout extends LayoutManager {
}
@Override
- public float intrinsicHeight(@NonNull RemoteContext context) {
+ public float minIntrinsicHeight(@NonNull RemoteContext context) {
float height = computeModifierDefinedHeight(context);
float componentHeights = 0f;
for (Component c : mChildrenComponents) {
- componentHeights += c.intrinsicHeight(context);
+ componentHeights += c.minIntrinsicHeight(context);
}
return Math.max(height, componentHeights);
}
@@ -225,7 +225,7 @@ public class ColumnLayout extends LayoutManager {
float totalWeights = 0f;
for (Component child : mChildrenComponents) {
ComponentMeasure childMeasure = measure.get(child);
- if (childMeasure.getVisibility() == Visibility.GONE) {
+ if (childMeasure.isGone()) {
continue;
}
if (child instanceof LayoutComponent
@@ -242,7 +242,7 @@ public class ColumnLayout extends LayoutManager {
if (child instanceof LayoutComponent
&& ((LayoutComponent) child).getHeightModifier().hasWeight()) {
ComponentMeasure childMeasure = measure.get(child);
- if (childMeasure.getVisibility() == Visibility.GONE) {
+ if (childMeasure.isGone()) {
continue;
}
float weight = ((LayoutComponent) child).getHeightModifier().getValue();
@@ -280,7 +280,7 @@ public class ColumnLayout extends LayoutManager {
int visibleChildrens = 0;
for (Component child : mChildrenComponents) {
ComponentMeasure childMeasure = measure.get(child);
- if (childMeasure.getVisibility() == Visibility.GONE) {
+ if (childMeasure.isGone()) {
continue;
}
childrenWidth = Math.max(childrenWidth, childMeasure.getW());
@@ -307,17 +307,22 @@ public class ColumnLayout extends LayoutManager {
case SPACE_BETWEEN:
for (Component child : mChildrenComponents) {
ComponentMeasure childMeasure = measure.get(child);
- if (childMeasure.getVisibility() == Visibility.GONE) {
+ if (childMeasure.isGone()) {
continue;
}
total += childMeasure.getH();
}
- verticalGap = (selfHeight - total) / (visibleChildrens - 1);
+ if (visibleChildrens > 1) {
+ verticalGap = (selfHeight - total) / (visibleChildrens - 1);
+ } else {
+ // we center the element
+ ty = (selfHeight - childrenHeight) / 2f;
+ }
break;
case SPACE_EVENLY:
for (Component child : mChildrenComponents) {
ComponentMeasure childMeasure = measure.get(child);
- if (childMeasure.getVisibility() == Visibility.GONE) {
+ if (childMeasure.isGone()) {
continue;
}
total += childMeasure.getH();
@@ -328,7 +333,7 @@ public class ColumnLayout extends LayoutManager {
case SPACE_AROUND:
for (Component child : mChildrenComponents) {
ComponentMeasure childMeasure = measure.get(child);
- if (childMeasure.getVisibility() == Visibility.GONE) {
+ if (childMeasure.isGone()) {
continue;
}
total += childMeasure.getH();
@@ -353,7 +358,7 @@ public class ColumnLayout extends LayoutManager {
}
childMeasure.setX(tx);
childMeasure.setY(ty);
- if (childMeasure.getVisibility() == Visibility.GONE) {
+ if (childMeasure.isGone()) {
continue;
}
ty += childMeasure.getH();
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/managers/FitBoxLayout.java b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/managers/FitBoxLayout.java
new file mode 100644
index 000000000000..ff7a3af11f20
--- /dev/null
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/managers/FitBoxLayout.java
@@ -0,0 +1,371 @@
+/*
+ * 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.layout.managers;
+
+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.WireBuffer;
+import com.android.internal.widget.remotecompose.core.documentation.DocumentationBuilder;
+import com.android.internal.widget.remotecompose.core.operations.layout.Component;
+import com.android.internal.widget.remotecompose.core.operations.layout.LayoutComponent;
+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.layout.modifiers.HeightInModifierOperation;
+import com.android.internal.widget.remotecompose.core.operations.layout.modifiers.HeightModifierOperation;
+import com.android.internal.widget.remotecompose.core.operations.layout.modifiers.WidthInModifierOperation;
+import com.android.internal.widget.remotecompose.core.operations.layout.modifiers.WidthModifierOperation;
+import com.android.internal.widget.remotecompose.core.serialize.MapSerializer;
+
+import java.util.List;
+
+/** FitBox layout implementation -- only display the child that fits in the available space */
+public class FitBoxLayout extends LayoutManager {
+
+ public static final int START = 1;
+ public static final int CENTER = 2;
+ public static final int END = 3;
+ public static final int TOP = 4;
+ public static final int BOTTOM = 5;
+
+ int mHorizontalPositioning;
+ int mVerticalPositioning;
+
+ public FitBoxLayout(
+ @Nullable Component parent,
+ int componentId,
+ int animationId,
+ float x,
+ float y,
+ float width,
+ float height,
+ int horizontalPositioning,
+ int verticalPositioning) {
+ super(parent, componentId, animationId, x, y, width, height);
+ mHorizontalPositioning = horizontalPositioning;
+ mVerticalPositioning = verticalPositioning;
+ }
+
+ public FitBoxLayout(
+ @Nullable Component parent,
+ int componentId,
+ int animationId,
+ int horizontalPositioning,
+ int verticalPositioning) {
+ this(
+ parent,
+ componentId,
+ animationId,
+ 0,
+ 0,
+ 0,
+ 0,
+ horizontalPositioning,
+ verticalPositioning);
+ }
+
+ @NonNull
+ @Override
+ public String toString() {
+ return "BOX ["
+ + mComponentId
+ + ":"
+ + mAnimationId
+ + "] ("
+ + mX
+ + ", "
+ + mY
+ + " - "
+ + mWidth
+ + " x "
+ + mHeight
+ + ") "
+ + mVisibility;
+ }
+
+ @NonNull
+ @Override
+ protected String getSerializedName() {
+ return "FITBOX";
+ }
+
+ @Override
+ public void computeWrapSize(
+ @NonNull PaintContext context,
+ float maxWidth,
+ float maxHeight,
+ boolean horizontalWrap,
+ boolean verticalWrap,
+ @NonNull MeasurePass measure,
+ @NonNull Size size) {
+
+ boolean found = false;
+ ComponentMeasure self = measure.get(this);
+ for (Component c : mChildrenComponents) {
+ float cw = 0f; // c.intrinsicWidth(context.getContext());
+ float ch = 0f; // c.intrinsicHeight(context.getContext());
+ if (c instanceof LayoutComponent) {
+ LayoutComponent lc = (LayoutComponent) c;
+ WidthModifierOperation widthModifier = lc.getWidthModifier();
+ if (widthModifier != null) {
+ WidthInModifierOperation widthIn = lc.getWidthModifier().getWidthIn();
+ if (widthIn != null) {
+ cw = widthIn.getMin();
+ }
+ }
+ HeightModifierOperation heightModifier = lc.getHeightModifier();
+ if (heightModifier != null) {
+ HeightInModifierOperation heightIn = lc.getHeightModifier().getHeightIn();
+ if (heightIn != null) {
+ ch = heightIn.getMin();
+ }
+ }
+ }
+ c.measure(context, 0f, maxWidth, 0f, maxHeight, measure);
+ ComponentMeasure m = measure.get(c);
+ if (!found && cw <= maxWidth && ch <= maxHeight) {
+ found = true;
+ m.addVisibilityOverride(Visibility.OVERRIDE_VISIBLE);
+ size.setWidth(m.getW());
+ size.setHeight(m.getH());
+ } else {
+ m.addVisibilityOverride(Visibility.OVERRIDE_GONE);
+ }
+ }
+ if (!found) {
+ self.setVisibility(Visibility.GONE);
+ } else {
+ self.setVisibility(Visibility.VISIBLE);
+ }
+
+ // add padding
+ size.setWidth(Math.max(size.getWidth(), computeModifierDefinedWidth(context.getContext())));
+ size.setHeight(
+ Math.max(size.getHeight(), computeModifierDefinedHeight(context.getContext())));
+ }
+
+ @Override
+ public void computeSize(
+ @NonNull PaintContext context,
+ float minWidth,
+ float maxWidth,
+ float minHeight,
+ float maxHeight,
+ @NonNull MeasurePass measure) {
+
+ ComponentMeasure self = measure.get(this);
+ boolean found = false;
+ for (Component c : mChildrenComponents) {
+ float cw = 0f;
+ float ch = 0f;
+ if (c instanceof LayoutComponent) {
+ LayoutComponent lc = (LayoutComponent) c;
+ WidthModifierOperation widthModifier = lc.getWidthModifier();
+ if (widthModifier != null) {
+ WidthInModifierOperation widthIn = lc.getWidthModifier().getWidthIn();
+ if (widthIn != null) {
+ cw = widthIn.getMin();
+ }
+ }
+ HeightModifierOperation heightModifier = lc.getHeightModifier();
+ if (heightModifier != null) {
+ HeightInModifierOperation heightIn = lc.getHeightModifier().getHeightIn();
+ if (heightIn != null) {
+ ch = heightIn.getMin();
+ }
+ }
+ }
+ c.measure(context, minWidth, maxWidth, minHeight, maxHeight, measure);
+ // child.measure(context, minWidth, Float.MAX_VALUE, minHeight,
+ // Float.MAX_VALUE, measure);
+ // m.getVisibility().clearOverride();
+ ComponentMeasure m = measure.get(c);
+ // m.setVisibility(Visibility.GONE);
+ // m.getVisibility().add(Visibility.OVERRIDE_GONE);
+ // m.getVisibility().add(Visibility.OVERRIDE_GONE);
+ m.clearVisibilityOverride();
+ if (!found && cw <= maxWidth && ch <= maxHeight) {
+ found = true;
+ m.addVisibilityOverride(Visibility.OVERRIDE_VISIBLE);
+ } else {
+ m.addVisibilityOverride(Visibility.OVERRIDE_GONE);
+ }
+ }
+ }
+
+ @Override
+ public void internalLayoutMeasure(@NonNull PaintContext context, @NonNull MeasurePass measure) {
+ ComponentMeasure selfMeasure = measure.get(this);
+ float selfWidth = selfMeasure.getW() - mPaddingLeft - mPaddingRight;
+ float selfHeight = selfMeasure.getH() - mPaddingTop - mPaddingBottom;
+ applyVisibility(selfWidth, selfHeight, measure);
+ for (Component child : mChildrenComponents) {
+ ComponentMeasure m = measure.get(child);
+ float tx = 0f;
+ float ty = 0f;
+ switch (mVerticalPositioning) {
+ case TOP:
+ ty = 0f;
+ break;
+ case CENTER:
+ ty = (selfHeight - m.getH()) / 2f;
+ break;
+ case BOTTOM:
+ ty = selfHeight - m.getH();
+ break;
+ }
+ switch (mHorizontalPositioning) {
+ case START:
+ tx = 0f;
+ break;
+ case CENTER:
+ tx = (selfWidth - m.getW()) / 2f;
+ break;
+ case END:
+ tx = selfWidth - m.getW();
+ break;
+ }
+ m.setX(tx);
+ m.setY(ty);
+ }
+ }
+
+ /**
+ * The name of the class
+ *
+ * @return the name
+ */
+ @NonNull
+ public static String name() {
+ return "BoxLayout";
+ }
+
+ /**
+ * The OP_CODE for this command
+ *
+ * @return the opcode
+ */
+ public static int id() {
+ return Operations.LAYOUT_FIT_BOX;
+ }
+
+ /**
+ * Write the operation to the buffer
+ *
+ * @param buffer a WireBuffer
+ * @param componentId the component id
+ * @param animationId the component animation id
+ * @param horizontalPositioning the horizontal positioning rules
+ * @param verticalPositioning the vertical positioning rules
+ */
+ public static void apply(
+ @NonNull WireBuffer buffer,
+ int componentId,
+ int animationId,
+ int horizontalPositioning,
+ int verticalPositioning) {
+ buffer.start(id());
+ buffer.writeInt(componentId);
+ buffer.writeInt(animationId);
+ buffer.writeInt(horizontalPositioning);
+ buffer.writeInt(verticalPositioning);
+ }
+
+ /**
+ * 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 horizontalPositioning = buffer.readInt();
+ int verticalPositioning = buffer.readInt();
+ operations.add(
+ new FitBoxLayout(
+ null,
+ componentId,
+ animationId,
+ horizontalPositioning,
+ verticalPositioning));
+ }
+
+ /**
+ * 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(
+ "FitBox layout implementation.\n\n"
+ + "Only display the first child component that fits in the available"
+ + " space")
+ .examplesDimension(150, 100)
+ .exampleImage("Top", "layout-BoxLayout-start-top.png")
+ .exampleImage("Center", "layout-BoxLayout-center-center.png")
+ .exampleImage("Bottom", "layout-BoxLayout-end-bottom.png")
+ .field(INT, "COMPONENT_ID", "unique id for this component")
+ .field(
+ INT,
+ "ANIMATION_ID",
+ "id used to match components," + " for animation purposes")
+ .field(INT, "HORIZONTAL_POSITIONING", "horizontal positioning value")
+ .possibleValues("START", FitBoxLayout.START)
+ .possibleValues("CENTER", FitBoxLayout.CENTER)
+ .possibleValues("END", FitBoxLayout.END)
+ .field(INT, "VERTICAL_POSITIONING", "vertical positioning value")
+ .possibleValues("TOP", FitBoxLayout.TOP)
+ .possibleValues("CENTER", FitBoxLayout.CENTER)
+ .possibleValues("BOTTOM", FitBoxLayout.BOTTOM);
+ }
+
+ @Override
+ public void write(@NonNull WireBuffer buffer) {
+ apply(buffer, mComponentId, mAnimationId, mHorizontalPositioning, mVerticalPositioning);
+ }
+
+ @Override
+ public void serialize(MapSerializer serializer) {
+ super.serialize(serializer);
+ serializer.add("verticalPositioning", getPositioningString(mVerticalPositioning));
+ serializer.add("horizontalPositioning", getPositioningString(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/LayoutManager.java b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/managers/LayoutManager.java
index 8b52bbe5cdf8..5b66b95cf1dd 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/managers/LayoutManager.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/managers/LayoutManager.java
@@ -73,19 +73,19 @@ public abstract class LayoutManager extends LayoutComponent implements Measurabl
}
@Override
- public float intrinsicHeight(@Nullable RemoteContext context) {
+ public float minIntrinsicHeight(@Nullable RemoteContext context) {
float height = computeModifierDefinedHeight(context);
for (Component c : mChildrenComponents) {
- height = Math.max(c.intrinsicHeight(context), height);
+ height = Math.max(c.minIntrinsicHeight(context), height);
}
return height;
}
@Override
- public float intrinsicWidth(@Nullable RemoteContext context) {
+ public float minIntrinsicWidth(@Nullable RemoteContext context) {
float width = computeModifierDefinedWidth(context);
for (Component c : mChildrenComponents) {
- width = Math.max(c.intrinsicWidth(context), width);
+ width = Math.max(c.minIntrinsicWidth(context), width);
}
return width;
}
@@ -149,10 +149,10 @@ public abstract class LayoutManager extends LayoutComponent implements Measurabl
Math.min(maxHeight, computeModifierDefinedHeight(context.getContext()));
if (mWidthModifier.isIntrinsicMin()) {
- maxWidth = intrinsicWidth(context.getContext()) + mPaddingLeft + mPaddingRight;
+ maxWidth = minIntrinsicWidth(context.getContext()) + mPaddingLeft + mPaddingRight;
}
if (mHeightModifier.isIntrinsicMin()) {
- maxHeight = intrinsicHeight(context.getContext()) + mPaddingTop + mPaddingBottom;
+ maxHeight = minIntrinsicHeight(context.getContext()) + mPaddingTop + mPaddingBottom;
}
float insetMaxWidth = maxWidth - mPaddingLeft - mPaddingRight;
@@ -171,6 +171,11 @@ public abstract class LayoutManager extends LayoutComponent implements Measurabl
mHeightModifier.isWrap(),
measure,
mCachedWrapSize);
+ int selfVisibilityAfterMeasure = measure.get(this).getVisibility();
+ if (Visibility.hasOverride(selfVisibilityAfterMeasure)
+ && mScheduledVisibility != selfVisibilityAfterMeasure) {
+ mScheduledVisibility = selfVisibilityAfterMeasure;
+ }
measuredWidth = mCachedWrapSize.getWidth();
if (hasHorizontalWrap) {
measuredWidth += mPaddingLeft + mPaddingRight;
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/managers/RowLayout.java b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/managers/RowLayout.java
index e93cbd74b0b5..d5d2e03c3f2a 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/managers/RowLayout.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/managers/RowLayout.java
@@ -136,7 +136,7 @@ public class RowLayout extends LayoutManager {
for (Component c : mChildrenComponents) {
c.measure(context, 0f, currentMaxWidth, 0f, maxHeight, measure);
ComponentMeasure m = measure.get(c);
- if (m.getVisibility() != Visibility.GONE) {
+ if (!m.isGone()) {
size.setWidth(size.getWidth() + m.getW());
size.setHeight(Math.max(size.getHeight(), m.getH()));
visibleChildrens++;
@@ -162,7 +162,7 @@ public class RowLayout extends LayoutManager {
for (Component child : mChildrenComponents) {
child.measure(context, minWidth, mw, minHeight, maxHeight, measure);
ComponentMeasure m = measure.get(child);
- if (m.getVisibility() != Visibility.GONE) {
+ if (!m.isGone()) {
mw -= m.getW();
}
}
@@ -170,16 +170,26 @@ public class RowLayout extends LayoutManager {
}
@Override
- public float intrinsicWidth(@Nullable RemoteContext context) {
+ public float minIntrinsicWidth(@Nullable RemoteContext context) {
float width = computeModifierDefinedWidth(context);
float componentWidths = 0f;
for (Component c : mChildrenComponents) {
- componentWidths += c.intrinsicWidth(context);
+ componentWidths += c.minIntrinsicWidth(context);
}
return Math.max(width, componentWidths);
}
@Override
+ public float minIntrinsicHeight(@Nullable RemoteContext context) {
+ float height = computeModifierDefinedHeight(context);
+ float componentHeights = 0f;
+ for (Component c : mChildrenComponents) {
+ componentHeights = Math.max(componentHeights, c.minIntrinsicHeight(context));
+ }
+ return Math.max(height, componentHeights);
+ }
+
+ @Override
public void internalLayoutMeasure(@NonNull PaintContext context, @NonNull MeasurePass measure) {
ComponentMeasure selfMeasure = measure.get(this);
DebugLog.s(
@@ -225,7 +235,7 @@ public class RowLayout extends LayoutManager {
float totalWeights = 0f;
for (Component child : mChildrenComponents) {
ComponentMeasure childMeasure = measure.get(child);
- if (childMeasure.getVisibility() == Visibility.GONE) {
+ if (childMeasure.isGone()) {
continue;
}
if (child instanceof LayoutComponent
@@ -245,7 +255,7 @@ public class RowLayout extends LayoutManager {
if (child instanceof LayoutComponent
&& ((LayoutComponent) child).getWidthModifier().hasWeight()) {
ComponentMeasure childMeasure = measure.get(child);
- if (childMeasure.getVisibility() == Visibility.GONE) {
+ if (childMeasure.isGone()) {
continue;
}
float weight = ((LayoutComponent) child).getWidthModifier().getValue();
@@ -283,7 +293,7 @@ public class RowLayout extends LayoutManager {
int visibleChildrens = 0;
for (Component child : mChildrenComponents) {
ComponentMeasure childMeasure = measure.get(child);
- if (childMeasure.getVisibility() == Visibility.GONE) {
+ if (childMeasure.isGone()) {
continue;
}
childrenWidth += childMeasure.getW();
@@ -311,17 +321,22 @@ public class RowLayout extends LayoutManager {
case SPACE_BETWEEN:
for (Component child : mChildrenComponents) {
ComponentMeasure childMeasure = measure.get(child);
- if (childMeasure.getVisibility() == Visibility.GONE) {
+ if (childMeasure.isGone()) {
continue;
}
total += childMeasure.getW();
}
- horizontalGap = (selfWidth - total) / (visibleChildrens - 1);
+ if (visibleChildrens > 1) {
+ horizontalGap = (selfWidth - total) / (visibleChildrens - 1);
+ } else {
+ // we center the element
+ tx = (selfWidth - childrenWidth) / 2f;
+ }
break;
case SPACE_EVENLY:
for (Component child : mChildrenComponents) {
ComponentMeasure childMeasure = measure.get(child);
- if (childMeasure.getVisibility() == Visibility.GONE) {
+ if (childMeasure.isGone()) {
continue;
}
total += childMeasure.getW();
@@ -332,7 +347,7 @@ public class RowLayout extends LayoutManager {
case SPACE_AROUND:
for (Component child : mChildrenComponents) {
ComponentMeasure childMeasure = measure.get(child);
- if (childMeasure.getVisibility() == Visibility.GONE) {
+ if (childMeasure.isGone()) {
continue;
}
total += childMeasure.getW();
@@ -357,7 +372,7 @@ public class RowLayout extends LayoutManager {
}
childMeasure.setX(tx);
childMeasure.setY(ty);
- if (childMeasure.getVisibility() == Visibility.GONE) {
+ if (childMeasure.isGone()) {
continue;
}
tx += childMeasure.getW();
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/managers/StateLayout.java b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/managers/StateLayout.java
index ee16bc2f4459..0192d8438a75 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/managers/StateLayout.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/managers/StateLayout.java
@@ -413,7 +413,7 @@ public class StateLayout extends LayoutManager {
if (index != currentLayoutIndex && index != previousLayoutIndex) {
pane.mVisibility = Visibility.GONE;
}
- if (index == currentLayoutIndex && pane.mVisibility != Visibility.VISIBLE) {
+ if (index == currentLayoutIndex && !pane.isVisible()) {
pane.mVisibility = Visibility.VISIBLE;
}
index++;
@@ -511,7 +511,7 @@ public class StateLayout extends LayoutManager {
&& previousLayout.mAnimateMeasure == null) {
inTransition = false;
LayoutManager previous = getLayout(previousLayoutIndex);
- if (previous != currentLayout && previous.mVisibility != Visibility.GONE) {
+ if (previous != currentLayout && !previous.isGone()) {
previous.mVisibility = Visibility.GONE;
previous.needsRepaint();
}
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 d5db74b5ca51..2595a71abaa5 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
@@ -280,7 +280,7 @@ public class TextLayout extends LayoutManager implements VariableSupport, Access
+ " x "
+ mHeight
+ ") "
- + mVisibility;
+ + Visibility.toString(mVisibility);
}
@NonNull
@@ -308,7 +308,7 @@ public class TextLayout extends LayoutManager implements VariableSupport, Access
+ ", "
+ mHeight
+ "] "
- + mVisibility
+ + Visibility.toString(mVisibility)
+ " ("
+ mTextId
+ ":\""
@@ -343,7 +343,7 @@ public class TextLayout extends LayoutManager implements VariableSupport, Access
flags |= PaintContext.TEXT_COMPLEX;
}
context.getTextBounds(mTextId, 0, mCachedString.length(), flags, bounds);
- if (bounds[2] - bounds[1] > maxWidth && mMaxLines > 1) {
+ if (bounds[2] - bounds[1] > maxWidth && mMaxLines > 1 && maxWidth > 0f) {
mComputedTextLayout =
context.layoutComplexText(
mTextId,
@@ -375,12 +375,12 @@ public class TextLayout extends LayoutManager implements VariableSupport, Access
}
@Override
- public float intrinsicHeight(@Nullable RemoteContext context) {
+ public float minIntrinsicHeight(@Nullable RemoteContext context) {
return mTextH;
}
@Override
- public float intrinsicWidth(@Nullable RemoteContext context) {
+ public float minIntrinsicWidth(@Nullable RemoteContext context) {
return mTextW;
}
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/measure/ComponentMeasure.java b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/measure/ComponentMeasure.java
index 11ed9f435070..993441944c33 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/measure/ComponentMeasure.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/measure/ComponentMeasure.java
@@ -26,7 +26,7 @@ public class ComponentMeasure {
float mY;
float mW;
float mH;
- @NonNull Component.Visibility mVisibility = Component.Visibility.VISIBLE;
+ int mVisibility = Component.Visibility.VISIBLE;
public void setX(float value) {
mX = value;
@@ -60,16 +60,15 @@ public class ComponentMeasure {
return mH;
}
- public @NonNull Component.Visibility getVisibility() {
+ public int getVisibility() {
return mVisibility;
}
- public void setVisibility(@NonNull Component.Visibility visibility) {
+ public void setVisibility(int visibility) {
mVisibility = visibility;
}
- public ComponentMeasure(
- int id, float x, float y, float w, float h, @NonNull Component.Visibility visibility) {
+ public ComponentMeasure(int id, float x, float y, float w, float h, int visibility) {
this.mId = id;
this.mX = x;
this.mY = y;
@@ -114,4 +113,42 @@ public class ComponentMeasure {
public boolean same(@NonNull ComponentMeasure m) {
return mX == m.mX && mY == m.mY && mW == m.mW && mH == m.mH && mVisibility == m.mVisibility;
}
+
+ /**
+ * Returns true if the component will be gone
+ *
+ * @return true if gone
+ */
+ public boolean isGone() {
+ return Component.Visibility.isGone(mVisibility);
+ }
+
+ /**
+ * Returns true if the component will be visible
+ *
+ * @return true if visible
+ */
+ public boolean isVisible() {
+ return Component.Visibility.isVisible(mVisibility);
+ }
+
+ /**
+ * Returns true if the component will be invisible
+ *
+ * @return true if invisible
+ */
+ public boolean isInvisible() {
+ return Component.Visibility.isInvisible(mVisibility);
+ }
+
+ /** Clear any override on the visibility */
+ public void clearVisibilityOverride() {
+ mVisibility = Component.Visibility.clearOverride(mVisibility);
+ }
+
+ /** Add a visibility override */
+ public void addVisibilityOverride(int value) {
+ mVisibility = Component.Visibility.clearOverride(mVisibility);
+ mVisibility = Component.Visibility.add(mVisibility, value);
+ }
}
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/BackgroundModifierOperation.java b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/BackgroundModifierOperation.java
index fd5f8c9cdbe7..1ab0c3dc7fb8 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/BackgroundModifierOperation.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/BackgroundModifierOperation.java
@@ -228,7 +228,7 @@ public class BackgroundModifierOperation extends DecoratorModifierOperation {
public void serialize(MapSerializer serializer) {
serializer
.addTags(SerializeTags.MODIFIER)
- .add("type", "BackgroundModifierOperation")
+ .addType("BackgroundModifierOperation")
.add("x", mX)
.add("y", mY)
.add("width", mWidth)
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 e5f318307a75..656a3c0fca68 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
@@ -290,7 +290,7 @@ public class BorderModifierOperation extends DecoratorModifierOperation {
public void serialize(MapSerializer serializer) {
serializer
.addTags(SerializeTags.MODIFIER)
- .add("type", "BorderModifierOperation")
+ .addType("BorderModifierOperation")
.add("x", mX)
.add("y", mY)
.add("width", mWidth)
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/ClipRectModifierOperation.java b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/ClipRectModifierOperation.java
index 00a53170fc48..e96dc838fc91 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/ClipRectModifierOperation.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/ClipRectModifierOperation.java
@@ -111,7 +111,7 @@ public class ClipRectModifierOperation extends DecoratorModifierOperation {
public void serialize(MapSerializer serializer) {
serializer
.addTags(SerializeTags.MODIFIER)
- .add("type", "ClipRectModifierOperation")
+ .addType("ClipRectModifierOperation")
.add("width", mWidth)
.add("height", mHeight);
}
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/ComponentModifiers.java b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/ComponentModifiers.java
index a9e342144cc5..14b2fad5825d 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/ComponentModifiers.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/ComponentModifiers.java
@@ -327,7 +327,7 @@ public class ComponentModifiers extends PaintOperation
public void serialize(MapSerializer serializer) {
serializer
.addTags(SerializeTags.MODIFIER)
- .add("type", "ComponentModifiers")
+ .addType("ComponentModifiers")
.add("modifiers", mList);
}
}
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/ComponentVisibilityOperation.java b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/ComponentVisibilityOperation.java
index fbf8a95248b9..88b28c39eec6 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/ComponentVisibilityOperation.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/ComponentVisibilityOperation.java
@@ -41,7 +41,7 @@ public class ComponentVisibilityOperation extends Operation
private static final int OP_CODE = Operations.MODIFIER_VISIBILITY;
int mVisibilityId;
- @NonNull Component.Visibility mVisibility = Component.Visibility.VISIBLE;
+ int mVisibility = Component.Visibility.VISIBLE;
private LayoutComponent mParent;
public ComponentVisibilityOperation(int id) {
@@ -124,11 +124,11 @@ public class ComponentVisibilityOperation extends Operation
@Override
public void updateVariables(@NonNull RemoteContext context) {
int visibility = context.getInteger(mVisibilityId);
- if (visibility == Component.Visibility.VISIBLE.ordinal()) {
+ if (Component.Visibility.isVisible(visibility)) {
mVisibility = Component.Visibility.VISIBLE;
- } else if (visibility == Component.Visibility.GONE.ordinal()) {
+ } else if (Component.Visibility.isGone(visibility)) {
mVisibility = Component.Visibility.GONE;
- } else if (visibility == Component.Visibility.INVISIBLE.ordinal()) {
+ } else if (Component.Visibility.isInvisible(visibility)) {
mVisibility = Component.Visibility.INVISIBLE;
} else {
mVisibility = Component.Visibility.GONE;
@@ -150,8 +150,8 @@ public class ComponentVisibilityOperation extends Operation
public void serialize(MapSerializer serializer) {
serializer
.addTags(SerializeTags.MODIFIER)
- .add("type", "ComponentVisibilityOperation")
+ .addType("ComponentVisibilityOperation")
.add("visibilityId", mVisibilityId)
- .add("visibility", mVisibility);
+ .add("visibility", Component.Visibility.toString(mVisibility));
}
}
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/DrawContentOperation.java b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/DrawContentOperation.java
index d7abdbae4962..6beb13586163 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/DrawContentOperation.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/DrawContentOperation.java
@@ -120,6 +120,6 @@ public class DrawContentOperation extends Operation
@Override
public void serialize(MapSerializer serializer) {
- serializer.addTags(SerializeTags.MODIFIER).add("type", "DrawContentOperation");
+ serializer.addTags(SerializeTags.MODIFIER).addType("DrawContentOperation");
}
}
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/GraphicsLayerModifierOperation.java b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/GraphicsLayerModifierOperation.java
index c1c1f9568ea8..361438b51a47 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/GraphicsLayerModifierOperation.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/GraphicsLayerModifierOperation.java
@@ -352,7 +352,7 @@ public class GraphicsLayerModifierOperation extends DecoratorModifierOperation {
public void serialize(MapSerializer serializer) {
serializer
.addTags(SerializeTags.MODIFIER)
- .add("type", "GraphicsLayerModifierOperation")
+ .addType("GraphicsLayerModifierOperation")
.add("scaleX", mScaleX)
.add("scaleX", mScaleX)
.add("rotationX", mRotationX)
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/HeightInModifierOperation.java b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/HeightInModifierOperation.java
index 7f0dd8d8d8e5..9b63c034675a 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/HeightInModifierOperation.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/HeightInModifierOperation.java
@@ -107,7 +107,7 @@ public class HeightInModifierOperation extends DimensionInModifierOperation {
public void serialize(MapSerializer serializer) {
serializer
.addTags(SerializeTags.MODIFIER)
- .add("type", "HeightInModifierOperation")
+ .addType("HeightInModifierOperation")
.add("min", mV1, mValue1)
.add("max", mV2, mValue2);
}
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/HeightModifierOperation.java b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/HeightModifierOperation.java
index 1df84257f344..5fbaafca5759 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/HeightModifierOperation.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/HeightModifierOperation.java
@@ -143,7 +143,7 @@ public class HeightModifierOperation extends DimensionModifierOperation {
public void serialize(MapSerializer serializer) {
serializer
.addTags(SerializeTags.MODIFIER)
- .add("type", "HeightModifierOperation")
+ .addType("HeightModifierOperation")
.add("height", mValue, mOutValue)
.add("dimensionModifierType", mType);
}
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/HostActionOperation.java b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/HostActionOperation.java
index 67714ef5abac..4d8acb4f04e2 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/HostActionOperation.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/HostActionOperation.java
@@ -130,7 +130,7 @@ public class HostActionOperation extends Operation
public void serialize(MapSerializer serializer) {
serializer
.addTags(SerializeTags.MODIFIER)
- .add("type", "HostActionOperation")
+ .addType("HostActionOperation")
.add("id", mActionId);
}
}
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/HostNamedActionOperation.java b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/HostNamedActionOperation.java
index 40c13f143468..807ff68fa965 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/HostNamedActionOperation.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/HostNamedActionOperation.java
@@ -157,7 +157,7 @@ public class HostNamedActionOperation extends Operation implements ActionOperati
public void serialize(MapSerializer serializer) {
serializer
.addTags(SerializeTags.MODIFIER)
- .add("type", "HostNamedActionOperation")
+ .addType("HostNamedActionOperation")
.add("textId", mTextId)
.add("actionType", getActionType(mType))
.add("valueId", mValueId);
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/MarqueeModifierOperation.java b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/MarqueeModifierOperation.java
index d2a1684a262a..c493e253d243 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/MarqueeModifierOperation.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/MarqueeModifierOperation.java
@@ -247,8 +247,8 @@ public class MarqueeModifierOperation extends DecoratorModifierOperation impleme
mComponentHeight = height;
if (component instanceof LayoutComponent) {
LayoutComponent layoutComponent = (LayoutComponent) component;
- setContentWidth(layoutComponent.intrinsicWidth(context));
- setContentHeight(layoutComponent.intrinsicHeight(context));
+ setContentWidth(layoutComponent.minIntrinsicWidth(context));
+ setContentHeight(layoutComponent.minIntrinsicHeight(context));
}
}
@@ -256,7 +256,7 @@ public class MarqueeModifierOperation extends DecoratorModifierOperation impleme
public void serialize(MapSerializer serializer) {
serializer
.addTags(SerializeTags.MODIFIER)
- .add("type", "MarqueeModifierOperation")
+ .addType("MarqueeModifierOperation")
.add("iterations", mIterations)
.add("animationMode", mAnimationMode)
.add("repeatDelayMillis", mRepeatDelayMillis)
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/OffsetModifierOperation.java b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/OffsetModifierOperation.java
index b7fe97b0e936..37f56cbc02e1 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/OffsetModifierOperation.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/OffsetModifierOperation.java
@@ -162,7 +162,7 @@ public class OffsetModifierOperation extends DecoratorModifierOperation {
public void serialize(MapSerializer serializer) {
serializer
.addTags(SerializeTags.MODIFIER)
- .add("type", "OffsetModifierOperation")
+ .addType("OffsetModifierOperation")
.add("x", mX)
.add("y", mY);
}
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/PaddingModifierOperation.java b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/PaddingModifierOperation.java
index d5b3a0b09261..0156992650ed 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/PaddingModifierOperation.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/PaddingModifierOperation.java
@@ -184,7 +184,7 @@ public class PaddingModifierOperation extends Operation implements ModifierOpera
public void serialize(MapSerializer serializer) {
serializer
.addTags(SerializeTags.MODIFIER)
- .add("type", "PaddingModifierOperation")
+ .addType("PaddingModifierOperation")
.add("left", mLeft)
.add("top", mTop)
.add("right", mRight)
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/RippleModifierOperation.java b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/RippleModifierOperation.java
index 69ace8478e08..eb5bfcff7d40 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/RippleModifierOperation.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/RippleModifierOperation.java
@@ -217,7 +217,7 @@ public class RippleModifierOperation extends DecoratorModifierOperation implemen
public void serialize(MapSerializer serializer) {
serializer
.addTags(SerializeTags.MODIFIER)
- .add("type", "RippleModifierOperation")
+ .addType("RippleModifierOperation")
.add("animateRippleStart", mAnimateRippleStart)
.add("animateRippleX", mAnimateRippleX)
.add("animateRippleY", mAnimateRippleY)
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/RoundedClipRectModifierOperation.java b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/RoundedClipRectModifierOperation.java
index 8442e05e59df..f0ed905ac158 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/RoundedClipRectModifierOperation.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/RoundedClipRectModifierOperation.java
@@ -167,7 +167,7 @@ public class RoundedClipRectModifierOperation extends DrawBase4
public void serialize(MapSerializer serializer) {
serialize(serializer, "topStart", "topEnd", "bottomStart", "bottomEnd")
.addTags(SerializeTags.MODIFIER)
- .add("type", CLASS_NAME)
+ .addType(CLASS_NAME)
.add("width", mWidth)
.add("height", mHeight);
}
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 a57365ec9132..466e435e20cf 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
@@ -397,7 +397,7 @@ public class ScrollModifierOperation extends ListActionsOperation
public void serialize(MapSerializer serializer) {
serializer
.addTags(SerializeTags.MODIFIER)
- .add("type", "ScrollModifierOperation")
+ .addType("ScrollModifierOperation")
.add("direction", mDirection)
.add("max", mMax)
.add("notchMax", mNotchMax)
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/ValueFloatChangeActionOperation.java b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/ValueFloatChangeActionOperation.java
index bd91734602d9..171e2bed6b00 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/ValueFloatChangeActionOperation.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/ValueFloatChangeActionOperation.java
@@ -126,7 +126,7 @@ public class ValueFloatChangeActionOperation extends Operation implements Action
public void serialize(MapSerializer serializer) {
serializer
.addTags(SerializeTags.MODIFIER, SerializeTags.ACTION)
- .add("type", "ValueFloatChangeActionOperation")
+ .addType("ValueFloatChangeActionOperation")
.add("targetValueId", mTargetValueId)
.add("value", mValue);
}
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/ValueFloatExpressionChangeActionOperation.java b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/ValueFloatExpressionChangeActionOperation.java
index 4b18d0aaf0df..d8133f6715a8 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/ValueFloatExpressionChangeActionOperation.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/ValueFloatExpressionChangeActionOperation.java
@@ -133,7 +133,7 @@ public class ValueFloatExpressionChangeActionOperation extends Operation
public void serialize(MapSerializer serializer) {
serializer
.addTags(SerializeTags.MODIFIER, SerializeTags.ACTION)
- .add("type", "ValueFloatExpressionChangeActionOperation")
+ .addType("ValueFloatExpressionChangeActionOperation")
.add("targetValueId", mTargetValueId)
.add("valueExpressionId", mValueExpressionId);
}
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/ValueIntegerChangeActionOperation.java b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/ValueIntegerChangeActionOperation.java
index d86c4a6ad416..05a6fd0f10da 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/ValueIntegerChangeActionOperation.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/ValueIntegerChangeActionOperation.java
@@ -131,7 +131,7 @@ public class ValueIntegerChangeActionOperation extends Operation implements Acti
public void serialize(MapSerializer serializer) {
serializer
.addTags(SerializeTags.MODIFIER, SerializeTags.ACTION)
- .add("type", "ValueIntegerChangeActionOperation")
+ .addType("ValueIntegerChangeActionOperation")
.add("targetValueId", mTargetValueId)
.add("value", mValue);
}
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/ValueIntegerExpressionChangeActionOperation.java b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/ValueIntegerExpressionChangeActionOperation.java
index e253460d2afb..8994febe7f7c 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/ValueIntegerExpressionChangeActionOperation.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/ValueIntegerExpressionChangeActionOperation.java
@@ -133,7 +133,7 @@ public class ValueIntegerExpressionChangeActionOperation extends Operation
public void serialize(MapSerializer serializer) {
serializer
.addTags(SerializeTags.MODIFIER, SerializeTags.ACTION)
- .add("type", "ValueIntegerExpressionChangeActionOperation")
+ .addType("ValueIntegerExpressionChangeActionOperation")
.add("targetValueId", mTargetValueId)
.add("valueExpressionId", mValueExpressionId);
}
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/ValueStringChangeActionOperation.java b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/ValueStringChangeActionOperation.java
index e84b29922523..08960d3576f4 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/ValueStringChangeActionOperation.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/ValueStringChangeActionOperation.java
@@ -139,7 +139,7 @@ public class ValueStringChangeActionOperation extends Operation implements Actio
public void serialize(MapSerializer serializer) {
serializer
.addTags(SerializeTags.MODIFIER, SerializeTags.ACTION)
- .add("type", "ValueIntegerExpressionChangeActionOperation")
+ .addType("ValueIntegerExpressionChangeActionOperation")
.add("targetValueId", mTargetValueId)
.add("valueId", mValueId);
}
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/WidthInModifierOperation.java b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/WidthInModifierOperation.java
index 3282a9cb5426..93074c79319f 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/WidthInModifierOperation.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/WidthInModifierOperation.java
@@ -107,7 +107,7 @@ public class WidthInModifierOperation extends DimensionInModifierOperation {
public void serialize(MapSerializer serializer) {
serializer
.addTags(SerializeTags.MODIFIER)
- .add("type", "WidthInModifierOperation")
+ .addType("WidthInModifierOperation")
.add("min", mV1, mValue1)
.add("max", mV2, mValue2);
}
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/WidthModifierOperation.java b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/WidthModifierOperation.java
index 6fe5a70b7ad4..ebdafa2aaa89 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/WidthModifierOperation.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/WidthModifierOperation.java
@@ -143,7 +143,7 @@ public class WidthModifierOperation extends DimensionModifierOperation {
public void serialize(MapSerializer serializer) {
serializer
.addTags(SerializeTags.MODIFIER)
- .add("type", "WidthModifierOperation")
+ .addType("WidthModifierOperation")
.add("width", mValue, mOutValue)
.add("dimensionModifierType", mType);
}
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/ZIndexModifierOperation.java b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/ZIndexModifierOperation.java
index f250951ab87e..ddb34b57e936 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/ZIndexModifierOperation.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/ZIndexModifierOperation.java
@@ -147,7 +147,7 @@ public class ZIndexModifierOperation extends DecoratorModifierOperation {
public void serialize(MapSerializer serializer) {
serializer
.addTags(SerializeTags.MODIFIER)
- .add("type", "ZIndexModifierOperation")
+ .addType("ZIndexModifierOperation")
.add("value", mValue);
}
}
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/paint/PaintBundle.java b/core/java/com/android/internal/widget/remotecompose/core/operations/paint/PaintBundle.java
index 0f17b114133d..55b64364e4e5 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/paint/PaintBundle.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/paint/PaintBundle.java
@@ -1248,7 +1248,7 @@ public class PaintBundle implements Serializable {
@Override
public void serialize(MapSerializer serializer) {
- serializer.add("type", "PaintBundle");
+ serializer.addType("PaintBundle");
List<Map<String, Object>> list = new ArrayList<>();
int i = 0;
while (i < mPos) {
@@ -1336,6 +1336,7 @@ public class PaintBundle implements Serializable {
serializer.add("operations", list);
}
+ @SuppressWarnings("JdkImmutableCollections")
private static Map<String, Object> getVariable(int value) {
float fValue = Float.intBitsToFloat(value);
if (Float.isNaN(fValue)) {
@@ -1344,6 +1345,7 @@ public class PaintBundle implements Serializable {
return orderedOf("type", "Value", "value", fValue);
}
+ @SuppressWarnings("JdkImmutableCollections")
private static int serializeGradient(
int cmd, int[] array, int i, List<Map<String, Object>> list) {
int ret = i;
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/utilities/easing/FloatAnimation.java b/core/java/com/android/internal/widget/remotecompose/core/operations/utilities/easing/FloatAnimation.java
index cad76059f7a4..349ab6117b2b 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/utilities/easing/FloatAnimation.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/utilities/easing/FloatAnimation.java
@@ -34,8 +34,10 @@ public class FloatAnimation extends Easing implements Serializable {
private float mWrap = Float.NaN;
private float mInitialValue = Float.NaN;
private float mTargetValue = Float.NaN;
+ private int mDirectionalSnap = 0;
// private float mScale = 1;
float mOffset = 0;
+ private boolean mPropagate = false;
@NonNull
@Override
@@ -161,11 +163,15 @@ public class FloatAnimation extends Easing implements Serializable {
int type = 0;
float wrapValue = Float.NaN;
float initialValue = Float.NaN;
+ int directionalSnap = 0;
+ boolean propagate = false;
if (mSpec.length > 1) {
int num_type = Float.floatToRawIntBits(mSpec[1]);
type = num_type & 0xFF;
boolean wrap = ((num_type >> 8) & 0x1) > 0;
boolean init = ((num_type >> 8) & 0x2) > 0;
+ directionalSnap = (num_type >> 10) & 0x3;
+ propagate = ((num_type >> 12) & 0x1) > 0;
len = (num_type >> 16) & 0xFFFF;
int off = 2 + len;
if (init) {
@@ -229,6 +235,12 @@ public class FloatAnimation extends Easing implements Serializable {
if (!Float.isNaN(wrapValue)) {
str += " wrap =" + wrapValue;
}
+ if (directionalSnap != 0) {
+ str += " directionalSnap=" + directionalSnap;
+ }
+ if (propagate) {
+ str += " propagate";
+ }
return str;
}
@@ -246,6 +258,8 @@ public class FloatAnimation extends Easing implements Serializable {
mType = num_type & 0xFF;
boolean wrap = ((num_type >> 8) & 0x1) > 0;
boolean init = ((num_type >> 8) & 0x2) > 0;
+ int directional = (num_type >> 10) & 0x3;
+ boolean propagate = ((num_type >> 12) & 0x1) > 0;
len = (num_type >> 16) & 0xFFFF;
int off = 2 + len;
if (init) {
@@ -254,6 +268,8 @@ public class FloatAnimation extends Easing implements Serializable {
if (wrap) {
mWrap = mSpec[off];
}
+ mDirectionalSnap = directional;
+ mPropagate = propagate;
}
create(mType, description, 2, len);
}
@@ -347,7 +363,13 @@ public class FloatAnimation extends Easing implements Serializable {
float dist = wrapDistance(mWrap, mInitialValue, mTargetValue);
if ((dist > 0) && (mTargetValue < mInitialValue)) {
mTargetValue += mWrap;
- } else if ((dist < 0) && (mTargetValue > mInitialValue)) {
+ } else if ((dist < 0) && mDirectionalSnap != 0) {
+ if (mDirectionalSnap == 1 && mTargetValue > mInitialValue) {
+ mInitialValue = mTargetValue;
+ }
+ if (mDirectionalSnap == 2 && mTargetValue < mInitialValue) {
+ mInitialValue = mTargetValue;
+ }
mTargetValue -= mWrap;
}
}
@@ -377,6 +399,14 @@ public class FloatAnimation extends Easing implements Serializable {
/** get the value at time t in seconds since start */
@Override
public float get(float t) {
+ if (mDirectionalSnap == 1 && mTargetValue < mInitialValue) {
+ mInitialValue = mTargetValue;
+ return mTargetValue;
+ }
+ if (mDirectionalSnap == 2 && mTargetValue > mInitialValue) {
+ mInitialValue = mTargetValue;
+ return mTargetValue;
+ }
return mEasingCurve.get(t / mDuration) * (mTargetValue - mInitialValue) + mInitialValue;
}
@@ -387,6 +417,13 @@ public class FloatAnimation extends Easing implements Serializable {
}
/**
+ * @return if you should propagate the animation
+ */
+ public boolean isPropagate() {
+ return mPropagate;
+ }
+
+ /**
* Get the initial value
*
* @return the initial value
@@ -398,7 +435,7 @@ public class FloatAnimation extends Easing implements Serializable {
@Override
public void serialize(MapSerializer serializer) {
serializer
- .add("type", "FloatAnimation")
+ .addType("FloatAnimation")
.add("initialValue", mInitialValue)
.add("targetValue", mInitialValue)
.add("duration", mInitialValue)
diff --git a/core/java/com/android/internal/widget/remotecompose/core/semantics/CoreSemantics.java b/core/java/com/android/internal/widget/remotecompose/core/semantics/CoreSemantics.java
index 08559fc21acb..424894a3e665 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/semantics/CoreSemantics.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/semantics/CoreSemantics.java
@@ -161,7 +161,7 @@ public class CoreSemantics extends Operation implements AccessibilityModifier {
public void serialize(MapSerializer serializer) {
serializer
.addTags(SerializeTags.MODIFIER, SerializeTags.A11Y)
- .add("type", "CoreSemantics")
+ .addType("CoreSemantics")
.add("contentDescriptionId", mContentDescriptionId)
.add("role", mRole)
.add("textId", mTextId)
diff --git a/core/java/com/android/internal/widget/remotecompose/core/serialize/MapSerializer.java b/core/java/com/android/internal/widget/remotecompose/core/serialize/MapSerializer.java
index f9ecf0f4f672..20e94ab5d898 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/serialize/MapSerializer.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/serialize/MapSerializer.java
@@ -25,11 +25,17 @@ import java.util.Map;
public interface MapSerializer {
/**
+ * Adds a "type" field with this value
+ *
+ * @param type The name of the type
+ */
+ MapSerializer addType(String type);
+
+ /**
* Add a float expression
*
- * @param key
- * @param value
- * @return
+ * @param key The key
+ * @param value The float src
*/
MapSerializer addFloatExpressionSrc(String key, float[] value);
diff --git a/core/java/com/android/internal/widget/remotecompose/core/types/BooleanConstant.java b/core/java/com/android/internal/widget/remotecompose/core/types/BooleanConstant.java
index cb759a61249a..0da543f3336c 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/types/BooleanConstant.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/types/BooleanConstant.java
@@ -131,6 +131,6 @@ public class BooleanConstant extends Operation implements Serializable {
@Override
public void serialize(MapSerializer serializer) {
- serializer.add("type", CLASS_NAME).add("id", mId).add("value", mValue);
+ serializer.addType(CLASS_NAME).add("id", mId).add("value", mValue);
}
}
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 c734f813ede3..bdc765968387 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
@@ -123,6 +123,6 @@ public class IntegerConstant extends Operation implements Serializable {
@Override
public void serialize(MapSerializer serializer) {
- serializer.add("type", CLASS_NAME).add("id", mId).add("value", mValue);
+ serializer.addType(CLASS_NAME).add("id", mId).add("value", 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 50509f3636b3..d071e0a21d22 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
@@ -35,9 +35,13 @@ public class LongConstant extends Operation implements Serializable {
private static final String CLASS_NAME = "LongConstant";
private static final int OP_CODE = Operations.DATA_LONG;
- private final long mValue;
+ private long mValue;
private final int mId;
+ /**
+ * @param id the id of the constant
+ * @param value the value of the constant
+ */
public LongConstant(int id, long value) {
mId = id;
mValue = value;
@@ -52,6 +56,15 @@ public class LongConstant extends Operation implements Serializable {
return mValue;
}
+ /**
+ * Set the value of the long constant
+ *
+ * @param value the value to set it to
+ */
+ public void setValue(long value) {
+ mValue = value;
+ }
+
@Override
public void write(@NonNull WireBuffer buffer) {
apply(buffer, mId, mValue);
@@ -114,6 +127,6 @@ public class LongConstant extends Operation implements Serializable {
@Override
public void serialize(MapSerializer serializer) {
- serializer.add("type", CLASS_NAME).add("id", mId).add("value", mValue);
+ serializer.addType(CLASS_NAME).add("id", mId).add("value", mValue);
}
}
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 1d1e579ebc2f..1f9a27429067 100644
--- a/core/java/com/android/internal/widget/remotecompose/player/RemoteComposePlayer.java
+++ b/core/java/com/android/internal/widget/remotecompose/player/RemoteComposePlayer.java
@@ -388,6 +388,16 @@ public class RemoteComposePlayer extends FrameLayout implements RemoteContextAwa
mInner.setColor(colorName, colorValue);
}
+ /**
+ * This sets long based on its name.
+ *
+ * @param name Name of the color
+ * @param value The new long value
+ */
+ public void setLong(String name, long value) {
+ mInner.setLong(name, value);
+ }
+
private void mapColors() {
String[] name = getNamedColors();
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 ac4a294b5e5e..b5aedd8d0231 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
@@ -290,8 +290,8 @@ public class AndroidPaintContext extends PaintContext {
}
if ((flags & PaintContext.TEXT_MEASURE_FONT_HEIGHT) != 0) {
- bounds[1] = Math.round(mCachedFontMetrics.top);
- bounds[3] = Math.round(mCachedFontMetrics.bottom);
+ bounds[1] = Math.round(mCachedFontMetrics.ascent);
+ bounds[3] = Math.round(mCachedFontMetrics.descent);
} else {
bounds[1] = mTmpRect.top;
bounds[3] = mTmpRect.bottom;
@@ -344,6 +344,7 @@ public class AndroidPaintContext extends PaintContext {
default:
}
staticLayoutBuilder.setMaxLines(maxLines);
+ staticLayoutBuilder.setIncludePad(false);
StaticLayout staticLayout = staticLayoutBuilder.build();
return new AndroidComputedTextLayout(
diff --git a/core/java/com/android/internal/widget/remotecompose/player/platform/AndroidPlatformServices.java b/core/java/com/android/internal/widget/remotecompose/player/platform/AndroidPlatformServices.java
index ba8d83bff51f..51c42fe111bd 100644
--- a/core/java/com/android/internal/widget/remotecompose/player/platform/AndroidPlatformServices.java
+++ b/core/java/com/android/internal/widget/remotecompose/player/platform/AndroidPlatformServices.java
@@ -60,6 +60,14 @@ public class AndroidPlatformServices implements Platform {
}
@Override
+ public boolean isAlpha8Image(@NonNull Object image) {
+ if (image instanceof Bitmap) {
+ return ((Bitmap) image).getConfig().equals(Bitmap.Config.ALPHA_8);
+ }
+ return false;
+ }
+
+ @Override
@Nullable
public float[] pathToFloatArray(@NonNull Object path) {
// if (path is RemotePath) {
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 14349b028819..b31c76056e38 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
@@ -20,6 +20,7 @@ import android.annotation.Nullable;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Canvas;
+import android.graphics.Paint;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.widget.remotecompose.core.RemoteContext;
@@ -30,6 +31,7 @@ import com.android.internal.widget.remotecompose.core.operations.FloatExpression
import com.android.internal.widget.remotecompose.core.operations.ShaderData;
import com.android.internal.widget.remotecompose.core.operations.utilities.ArrayAccess;
import com.android.internal.widget.remotecompose.core.operations.utilities.DataMap;
+import com.android.internal.widget.remotecompose.core.types.LongConstant;
import java.io.IOException;
import java.net.MalformedURLException;
@@ -141,6 +143,16 @@ public class AndroidRemoteContext extends RemoteContext {
}
@Override
+ public void setNamedLong(String name, long value) {
+ VarName entry = mVarNameHashMap.get(name);
+ if (entry != null) {
+ int id = entry.mId;
+ LongConstant longConstant = (LongConstant) mRemoteComposeState.getObject(id);
+ longConstant.setValue(value);
+ }
+ }
+
+ @Override
public void setNamedDataOverride(String dataName, Object value) {
if (mVarNameHashMap.get(dataName) != null) {
int id = mVarNameHashMap.get(dataName).mId;
@@ -215,6 +227,27 @@ public class AndroidRemoteContext extends RemoteContext {
case BitmapData.TYPE_PNG_8888:
image = BitmapFactory.decodeByteArray(data, 0, data.length);
break;
+ case BitmapData.TYPE_PNG_ALPHA_8:
+ image = decodePreferringAlpha8(data);
+
+ // If needed convert to ALPHA_8.
+ if (!image.getConfig().equals(Bitmap.Config.ALPHA_8)) {
+ Bitmap alpha8Bitmap =
+ Bitmap.createBitmap(
+ image.getWidth(),
+ image.getHeight(),
+ Bitmap.Config.ALPHA_8);
+ Canvas canvas = new Canvas(alpha8Bitmap);
+ Paint paint = new Paint();
+ paint.setXfermode(
+ new android.graphics.PorterDuffXfermode(
+ android.graphics.PorterDuff.Mode.SRC));
+ canvas.drawBitmap(image, 0, 0, paint);
+ image.recycle(); // Release resources
+
+ image = alpha8Bitmap;
+ }
+ break;
case BitmapData.TYPE_RAW8888:
image = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
int[] idata = new int[data.length / 4];
@@ -255,6 +288,12 @@ public class AndroidRemoteContext extends RemoteContext {
}
}
+ private Bitmap decodePreferringAlpha8(@NonNull byte[] data) {
+ BitmapFactory.Options options = new BitmapFactory.Options();
+ options.inPreferredConfig = Bitmap.Config.ALPHA_8;
+ return BitmapFactory.decodeByteArray(data, 0, data.length, options);
+ }
+
@Override
public void loadText(int id, @NonNull String text) {
if (!mRemoteComposeState.containsId(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 4d2dd05ca603..29cd40def562 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
@@ -200,6 +200,16 @@ public class RemoteComposeCanvas extends FrameLayout implements View.OnAttachSta
mARContext.setNamedColorOverride(colorName, colorValue);
}
+ /**
+ * set the value of a long associated with this name.
+ *
+ * @param name Name of color typically "android.xxx"
+ * @param value the long value
+ */
+ public void setLong(String name, long value) {
+ mARContext.setNamedLong(name, value);
+ }
+
public RemoteComposeDocument getDocument() {
return mDocument;
}
diff --git a/core/jni/Android.bp b/core/jni/Android.bp
index 92a841f9c290..748c5b48534f 100644
--- a/core/jni/Android.bp
+++ b/core/jni/Android.bp
@@ -21,7 +21,6 @@ soong_config_module_type {
config_namespace: "ANDROID",
bool_variables: [
"release_binder_death_recipient_weak_from_jni",
- "release_package_libandroid_runtime_punch_holes",
],
properties: [
"cflags",
@@ -66,9 +65,6 @@ cc_library_shared_for_libandroid_runtime {
release_binder_death_recipient_weak_from_jni: {
cflags: ["-DBINDER_DEATH_RECIPIENT_WEAK_FROM_JNI"],
},
- release_package_libandroid_runtime_punch_holes: {
- cflags: ["-DENABLE_PUNCH_HOLES"],
- },
},
cpp_std: "gnu++20",
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_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 06fd80e37669..7cf523f18a90 100644
--- a/core/jni/com_android_internal_content_NativeLibraryHelper.cpp
+++ b/core/jni/com_android_internal_content_NativeLibraryHelper.cpp
@@ -236,7 +236,6 @@ static install_status_t extractNativeLibFromApk(ZipFileRO* zipFile, ZipEntryRO z
return INSTALL_FAILED_CONTAINER_ERROR;
}
-#ifdef ENABLE_PUNCH_HOLES
// punch extracted elf files as well. This will fail where compression is on (like f2fs) but it
// will be useful for ext4 based systems
struct statfs64 fsInfo;
@@ -253,7 +252,6 @@ static install_status_t extractNativeLibFromApk(ZipFileRO* zipFile, ZipEntryRO z
zipFile->getZipFileName());
}
}
-#endif // ENABLE_PUNCH_HOLES
ALOGV("Successfully moved %s to %s\n", localTmpFileName, localFileName);
@@ -332,7 +330,6 @@ static install_status_t copyFileIfChanged(JNIEnv* env, void* arg, ZipFileRO* zip
return INSTALL_FAILED_INVALID_APK;
}
-#ifdef ENABLE_PUNCH_HOLES
// if library is uncompressed, punch hole in it in place
if (!punchHolesInElf64(zipFile->getZipFileName(), offset)) {
ALOGW("Failed to punch uncompressed elf file :%s inside apk : %s at offset: "
@@ -345,7 +342,6 @@ static install_status_t copyFileIfChanged(JNIEnv* env, void* arg, ZipFileRO* zip
if (!punchHolesInZip(zipFile->getZipFileName(), offset, extraFieldLength)) {
ALOGW("Failed to punch apk : %s at extra field", zipFile->getZipFileName());
}
-#endif // ENABLE_PUNCH_HOLES
return INSTALL_SUCCEEDED;
}
@@ -644,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/net/OWNERS b/core/proto/android/net/OWNERS
index 509699b9fb4b..a6627fe344b6 100644
--- a/core/proto/android/net/OWNERS
+++ b/core/proto/android/net/OWNERS
@@ -1,3 +1,2 @@
-ek@google.com
lorenzo@google.com
satk@google.com
diff --git a/core/proto/android/providers/settings/secure.proto b/core/proto/android/providers/settings/secure.proto
index 34ec1481f697..1caa9e7af348 100644
--- a/core/proto/android/providers/settings/secure.proto
+++ b/core/proto/android/providers/settings/secure.proto
@@ -111,7 +111,9 @@ message SecureSettingsProto {
optional SettingProto autoclick_cursor_area_size = 62 [ (android.privacy).dest = DEST_AUTOMATIC ];
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;
@@ -294,13 +296,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 {
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/AndroidManifest.xml b/core/res/AndroidManifest.xml
index ee6899cf866b..e16ce9849ff2 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -8995,13 +8995,13 @@
<!-- @SystemApi
@FlaggedApi("android.permission.flags.text_classifier_choice_api_enabled")
- This permission is required to access the specific text classifier you need from the
+ This permission is required to access the specific text classifier from the
TextClassificationManager.
- <p>Protection level: signature|role
+ <p>Protection level: signature|role|privileged
@hide
-->
<permission android:name="android.permission.ACCESS_TEXT_CLASSIFIER_BY_TYPE"
- android:protectionLevel="signature|role"
+ android:protectionLevel="signature|role|privileged"
android:featureFlag="android.permission.flags.text_classifier_choice_api_enabled"/>
<!-- Attribution for Geofencing service. -->
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/drawable/ic_accessibility_autoclick.xml b/core/res/res/drawable/ic_accessibility_autoclick.xml
new file mode 100644
index 000000000000..44d34d33e98e
--- /dev/null
+++ b/core/res/res/drawable/ic_accessibility_autoclick.xml
@@ -0,0 +1,8 @@
+<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
+ <background android:drawable="@color/accessibility_autoclick_background" />
+ <foreground>
+ <inset
+ android:drawable="@drawable/ic_accessibility_autoclick_foreground"
+ android:inset="@dimen/accessibility_icon_foreground_padding_ratio" />
+ </foreground>
+</adaptive-icon>
diff --git a/core/res/res/drawable/ic_accessibility_autoclick_foreground.xml b/core/res/res/drawable/ic_accessibility_autoclick_foreground.xml
new file mode 100644
index 000000000000..0a76a1b989e0
--- /dev/null
+++ b/core/res/res/drawable/ic_accessibility_autoclick_foreground.xml
@@ -0,0 +1,17 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="40dp"
+ android:height="41dp"
+ android:viewportWidth="40"
+ android:viewportHeight="41">
+
+ <path
+ android:fillColor="#67D4FF"
+ android:pathData="M0 20.3789C0 9.33321 8.95431 0.378906 20 0.378906C31.0457 0.378906 40 9.33321 40 20.3789C40 31.4246 31.0457 40.3789 20 40.3789C8.95431 40.3789 0 31.4246 0 20.3789Z" />
+ <path
+ android:fillColor="#04409F"
+ android:pathData="M28.9564 32.0587L24.0973 27.1996L22.6765 31.4904L19.2666 20.124L30.633 23.5339L26.3422 24.9547L31.2013 29.8138L28.9564 32.0587Z" />
+ <path
+ android:fillColor="#04409F"
+ android:fillType="evenOdd"
+ android:pathData="M8.01596 20.8918C8.10753 22.8222 8.63239 24.6433 9.60868 26.3343C10.9014 28.5733 12.8274 30.221 15.1666 31.2713C16.9169 32.0453 18.7173 32.3866 20.5679 32.2953L19.9158 30.1502C18.5615 30.1389 17.2456 29.8364 15.9682 29.2427C14.0941 28.3832 12.6373 27.0532 11.5977 25.2526C10.7841 23.8434 10.3491 22.3305 10.2925 20.7139C10.2607 19.104 10.2783 17.806 10.9916 16.3295C10.9916 16.3295 11.4971 15.2881 11.6974 14.9932C11.8978 14.6983 12.7644 13.5882 12.7644 13.5882L11.3665 11.8494C11.3665 11.8494 10.6288 12.6605 10.291 13.1593C10.1116 13.4242 9.64185 14.1332 9.64185 14.1332L9.07295 15.2304C8.24895 17.0214 7.92073 18.8842 8.01596 20.8918ZM31.9633 21.2755L29.8026 20.6995C29.8208 20.1549 29.797 19.6129 29.7312 19.0733C29.6285 18.1723 29.3858 17.2999 29.0031 16.4562L30.8819 15.3714C31.3937 16.4747 31.7295 17.6169 31.8895 18.798C32.0056 19.6202 32.0302 20.4461 31.9633 21.2755ZM18.4931 8.55776C17.312 8.71775 16.1653 9.04578 15.053 9.54184L16.1513 11.4442C16.995 11.0615 17.8674 10.8188 18.7684 10.7161C19.6851 10.6043 20.6091 10.6137 21.5403 10.7441L22.1061 8.63249C20.8942 8.41364 19.6899 8.38872 18.4931 8.55776ZM24.1807 9.18837L23.6149 11.3C24.4865 11.6526 25.2869 12.0987 26.0158 12.6382C26.7448 13.1776 27.379 13.824 27.9183 14.5773L29.7972 13.4925C29.1223 12.5043 28.3055 11.6502 27.347 10.9302C26.4042 10.2011 25.3487 9.62046 24.1807 9.18837Z" />
+</vector>
diff --git a/core/res/res/drawable/ic_notification_summarization.xml b/core/res/res/drawable/ic_notification_summarization.xml
index d476872a0e20..acfd90e807b3 100644
--- a/core/res/res/drawable/ic_notification_summarization.xml
+++ b/core/res/res/drawable/ic_notification_summarization.xml
@@ -16,9 +16,16 @@ Copyright (C) 2025 The Android Open Source Project
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="16dp"
android:height="16dp"
- android:tint="?android:attr/colorControlNormal"
- android:viewportHeight="960"
- android:viewportWidth="960">
- <path android:fillColor="#ffffff"
- android:pathData="M120,840L120,760L600,760L600,840L120,840ZM120,640L120,560L840,560L840,640L120,640ZM120,440L120,360L560,360L560,440L120,440ZM700,480Q700,388 636,324Q572,260 480,260Q572,260 636,196Q700,132 700,40Q700,132 764,196Q828,260 920,260Q828,260 764,324Q700,388 700,480Z"/>
+ android:tint="@color/materialColorPrimary"
+ android:viewportHeight="14"
+ android:viewportWidth="14">
+ <path
+ android:pathData="M10,9C9.986,9 9.979,8.993 9.979,8.979C9.979,8.431 9.875,7.917 9.667,7.438C9.465,6.958 9.184,6.538 8.823,6.177C8.462,5.816 8.042,5.535 7.563,5.333C7.083,5.125 6.569,5.021 6.021,5.021C6.007,5.021 6,5.014 6,5C6,4.986 6.007,4.979 6.021,4.979C6.569,4.979 7.083,4.878 7.563,4.677C8.042,4.469 8.462,4.184 8.823,3.823C9.184,3.462 9.465,3.042 9.667,2.563C9.875,2.083 9.979,1.569 9.979,1.021C9.979,1.007 9.986,1 10,1C10.014,1 10.021,1.007 10.021,1.021C10.021,1.569 10.122,2.083 10.323,2.563C10.531,3.042 10.816,3.462 11.177,3.823C11.538,4.184 11.958,4.469 12.438,4.677C12.917,4.878 13.431,4.979 13.979,4.979C13.993,4.979 14,4.986 14,5C14,5.014 13.993,5.021 13.979,5.021C13.431,5.021 12.917,5.125 12.438,5.333C11.958,5.535 11.538,5.816 11.177,6.177C10.816,6.538 10.531,6.958 10.323,7.438C10.122,7.917 10.021,8.431 10.021,8.979C10.021,8.993 10.014,9 10,9Z"
+ android:fillColor="#ffffff"/>
+ <path
+ android:pathData="M1,10.456C1,10.204 1.204,10 1.456,10H12.544C12.796,10 13,10.204 13,10.456V11.544C13,11.796 12.796,12 12.544,12H1.456C1.204,12 1,11.796 1,11.544V10.456Z"
+ android:fillColor="#ffffff"/>
+ <path
+ android:pathData="M1,7.456C1,7.204 1.204,7 1.456,7H6.544C6.796,7 7,7.204 7,7.456V8.544C7,8.796 6.796,9 6.544,9H1.456C1.204,9 1,8.796 1,8.544V7.456Z"
+ android:fillColor="#ffffff"/>
</vector> \ No newline at end of file
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 cedbdc175488..902ef7fc38e8 100644
--- a/core/res/res/layout/accessibility_autoclick_type_panel.xml
+++ b/core/res/res/layout/accessibility_autoclick_type_panel.xml
@@ -17,7 +17,7 @@
*/
-->
-<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+<com.android.server.accessibility.autoclick.AutoclickLinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/accessibility_autoclick_type_panel"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
@@ -130,4 +130,4 @@
</LinearLayout>
-</LinearLayout>
+</com.android.server.accessibility.autoclick.AutoclickLinearLayout>
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_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..63f32e3b3cd2 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,17 @@
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 +84,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 +122,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 +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"
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..fbea10d42b2b 100644
--- a/core/res/res/layout/notification_2025_template_collapsed_call.xml
+++ b/core/res/res/layout/notification_2025_template_collapsed_call.xml
@@ -128,15 +128,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 +139,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"
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..a6fdcd95399e 100644
--- a/core/res/res/layout/notification_2025_template_collapsed_conversation.xml
+++ b/core/res/res/layout/notification_2025_template_collapsed_conversation.xml
@@ -149,9 +149,9 @@
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_marginBottom="@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 +163,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"
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..629af77b3dda 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,17 @@
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 +86,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 +123,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 +144,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"
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..3716fa6825b3 100644
--- a/core/res/res/layout/notification_2025_template_collapsed_messaging.xml
+++ b/core/res/res/layout/notification_2025_template_collapsed_messaging.xml
@@ -159,9 +159,9 @@
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_marginBottom="@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 +173,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"
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..8d99e47c5386 100644
--- a/core/res/res/layout/notification_2025_template_expanded_base.xml
+++ b/core/res/res/layout/notification_2025_template_expanded_base.xml
@@ -63,7 +63,7 @@
/>
</LinearLayout>
- <include layout="@layout/notification_template_right_icon" />
+ <include layout="@layout/notification_2025_right_icon" />
</FrameLayout>
<ViewStub
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..e8e460d1b4ae 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,7 +25,7 @@
<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"
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..b68db7f0a638 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
@@ -91,5 +91,5 @@
<include layout="@layout/notification_material_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..7b45b55ba15b 100644
--- a/core/res/res/layout/notification_2025_template_expanded_call.xml
+++ b/core/res/res/layout/notification_2025_template_expanded_call.xml
@@ -68,6 +68,6 @@
</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..592785d53018 100644
--- a/core/res/res/layout/notification_2025_template_expanded_conversation.xml
+++ b/core/res/res/layout/notification_2025_template_expanded_conversation.xml
@@ -70,6 +70,6 @@
</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..6459e1eab862 100644
--- a/core/res/res/layout/notification_2025_template_expanded_inbox.xml
+++ b/core/res/res/layout/notification_2025_template_expanded_inbox.xml
@@ -130,5 +130,5 @@
android:layout_marginTop="@dimen/notification_content_margin" />
<include layout="@layout/notification_material_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..801e339b3a92 100644
--- a/core/res/res/layout/notification_2025_template_expanded_media.xml
+++ b/core/res/res/layout/notification_2025_template_expanded_media.xml
@@ -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..82c71527a291 100644
--- a/core/res/res/layout/notification_2025_template_expanded_messaging.xml
+++ b/core/res/res/layout/notification_2025_template_expanded_messaging.xml
@@ -70,6 +70,6 @@
</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..2ff252747fd2 100644
--- a/core/res/res/layout/notification_2025_template_expanded_progress.xml
+++ b/core/res/res/layout/notification_2025_template_expanded_progress.xml
@@ -99,7 +99,7 @@
</LinearLayout>
</LinearLayout>
- <include layout="@layout/notification_template_right_icon" />
+ <include layout="@layout/notification_2025_right_icon" />
</FrameLayout>
<ViewStub
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..9967209c3f49 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>
diff --git a/core/res/res/values-am/strings.xml b/core/res/res/values-am/strings.xml
index 29b153b23d3d..bdaf5a18a5ba 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>
diff --git a/core/res/res/values-ar/strings.xml b/core/res/res/values-ar/strings.xml
index 08d0af5f3a6f..3b0e78dbada1 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>
diff --git a/core/res/res/values-as/strings.xml b/core/res/res/values-as/strings.xml
index 9eab313b1b94..5d93b655d741 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>
diff --git a/core/res/res/values-az/strings.xml b/core/res/res/values-az/strings.xml
index 3aec62a8b8d5..c6a0adea6d6f 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>
diff --git a/core/res/res/values-b+sr+Latn/strings.xml b/core/res/res/values-b+sr+Latn/strings.xml
index 47203a992934..2b4dc578e610 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>
diff --git a/core/res/res/values-be/strings.xml b/core/res/res/values-be/strings.xml
index 8230f0c7739c..8230b1c45757 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>
diff --git a/core/res/res/values-bg/strings.xml b/core/res/res/values-bg/strings.xml
index d9471e68c8e1..1b0477f09538 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,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>
diff --git a/core/res/res/values-bn/strings.xml b/core/res/res/values-bn/strings.xml
index cc05d28ca630..2461a70e1ab8 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>
diff --git a/core/res/res/values-bs/strings.xml b/core/res/res/values-bs/strings.xml
index 9c553cedadc5..b8d3283491c3 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>
diff --git a/core/res/res/values-ca/strings.xml b/core/res/res/values-ca/strings.xml
index 3fa177ef2116..cbfa521c96e4 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>
diff --git a/core/res/res/values-cs/strings.xml b/core/res/res/values-cs/strings.xml
index 670ca33f0e1d..356cdc71b54a 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>
diff --git a/core/res/res/values-da/strings.xml b/core/res/res/values-da/strings.xml
index 47eb357b4087..efe3e103d0e1 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>
diff --git a/core/res/res/values-de/strings.xml b/core/res/res/values-de/strings.xml
index 06a35e628d27..fc0e9ed19b9d 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>
diff --git a/core/res/res/values-el/strings.xml b/core/res/res/values-el/strings.xml
index 8a12e0e907b1..58f5d9db4d68 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>
diff --git a/core/res/res/values-en-rAU/strings.xml b/core/res/res/values-en-rAU/strings.xml
index 3881f1de0002..320e63935fc9 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,8 @@
<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>
+ <!-- no translation found for package_updated_device_owner (7770195449213776218) -->
+ <skip />
<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>
diff --git a/core/res/res/values-en-rCA/strings.xml b/core/res/res/values-en-rCA/strings.xml
index e6c4047cbda8..85471d8a5efb 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>
diff --git a/core/res/res/values-en-rGB/strings.xml b/core/res/res/values-en-rGB/strings.xml
index 9680b83af481..730dd3b808e8 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,8 @@
<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>
+ <!-- no translation found for package_updated_device_owner (7770195449213776218) -->
+ <skip />
<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>
diff --git a/core/res/res/values-en-rIN/strings.xml b/core/res/res/values-en-rIN/strings.xml
index fbe1c0a09a1c..a9654e525df4 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,8 @@
<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>
+ <!-- no translation found for package_updated_device_owner (7770195449213776218) -->
+ <skip />
<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>
diff --git a/core/res/res/values-es-rUS/strings.xml b/core/res/res/values-es-rUS/strings.xml
index 54297a568091..036ecfc59d87 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,8 @@
<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>
+ <!-- no translation found for package_updated_device_owner (7770195449213776218) -->
+ <skip />
<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>
diff --git a/core/res/res/values-es/strings.xml b/core/res/res/values-es/strings.xml
index 5bd2bf5941c3..3553aea44c2d 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>
diff --git a/core/res/res/values-et/strings.xml b/core/res/res/values-et/strings.xml
index 0c928a6c601d..abecc1a76d3f 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>
diff --git a/core/res/res/values-eu/strings.xml b/core/res/res/values-eu/strings.xml
index 13f0452b2f3a..64dc7303e8c1 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>
diff --git a/core/res/res/values-fa/strings.xml b/core/res/res/values-fa/strings.xml
index 2eaf4c033d52..13083d0c04d4 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,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>
diff --git a/core/res/res/values-fi/strings.xml b/core/res/res/values-fi/strings.xml
index 280658c26735..145c5ee4bef1 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>
diff --git a/core/res/res/values-fr-rCA/strings.xml b/core/res/res/values-fr-rCA/strings.xml
index 1740ce536ea5..0ee4112ed458 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,8 @@
<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>
+ <!-- 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">"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>
diff --git a/core/res/res/values-fr/strings.xml b/core/res/res/values-fr/strings.xml
index 0564661de80d..a5b6f9a92dce 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>
diff --git a/core/res/res/values-gl/strings.xml b/core/res/res/values-gl/strings.xml
index 73bed7fa72da..332387ad0131 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>
diff --git a/core/res/res/values-gu/strings.xml b/core/res/res/values-gu/strings.xml
index 0c99096ff4fc..7a39d634508e 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>
diff --git a/core/res/res/values-hi/strings.xml b/core/res/res/values-hi/strings.xml
index df211dc458a7..2c43996064ea 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>
diff --git a/core/res/res/values-hr/strings.xml b/core/res/res/values-hr/strings.xml
index f701483453aa..d870a1dbda61 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>
diff --git a/core/res/res/values-hu/strings.xml b/core/res/res/values-hu/strings.xml
index e3d261e11da8..350d51dee68c 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,8 @@
<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>
+ <!-- no translation found for package_updated_device_owner (7770195449213776218) -->
+ <skip />
<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>
diff --git a/core/res/res/values-hy/strings.xml b/core/res/res/values-hy/strings.xml
index de62af5376c7..655af9395fa3 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,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>
diff --git a/core/res/res/values-in/strings.xml b/core/res/res/values-in/strings.xml
index 18ae5e9715f9..305d23a15c00 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>
diff --git a/core/res/res/values-is/strings.xml b/core/res/res/values-is/strings.xml
index a74ff1cadcb7..cdf89ad4424d 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,8 @@
<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>
+ <!-- no translation found for package_updated_device_owner (7770195449213776218) -->
+ <skip />
<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>
diff --git a/core/res/res/values-it/strings.xml b/core/res/res/values-it/strings.xml
index befd2b21776c..3b7d1ec750ee 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,8 @@
<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>
+ <!-- no translation found for package_updated_device_owner (7770195449213776218) -->
+ <skip />
<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>
@@ -2282,7 +2281,7 @@
<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>
diff --git a/core/res/res/values-iw/strings.xml b/core/res/res/values-iw/strings.xml
index 0b6c80af7dcb..cf7374868a50 100644
--- a/core/res/res/values-iw/strings.xml
+++ b/core/res/res/values-iw/strings.xml
@@ -1826,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>
@@ -1968,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>
diff --git a/core/res/res/values-ja/strings.xml b/core/res/res/values-ja/strings.xml
index 2f8133a135a1..f8bc2413fca0 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,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">"バッテリー セーバーを ON にすると、ダークモードが ON になります。また、バックグラウンド アクティビティ、一部の視覚効果、特定の機能、一部のネットワーク接続が制限されるか OFF になります。"</string>
diff --git a/core/res/res/values-ka/strings.xml b/core/res/res/values-ka/strings.xml
index ef154052843d..8aa0af3efc44 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,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>
diff --git a/core/res/res/values-kk/strings.xml b/core/res/res/values-kk/strings.xml
index b5756304e15f..3908427d9f97 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>
diff --git a/core/res/res/values-km/strings.xml b/core/res/res/values-km/strings.xml
index eeae49bf388a..dee23f65e8ca 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>
diff --git a/core/res/res/values-kn/strings.xml b/core/res/res/values-kn/strings.xml
index fd468b43fd33..c1a70b0f7276 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,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>
diff --git a/core/res/res/values-ko/strings.xml b/core/res/res/values-ko/strings.xml
index 0a8867088ecb..be4a28423a93 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>
diff --git a/core/res/res/values-ky/strings.xml b/core/res/res/values-ky/strings.xml
index 83a1c69afa92..121908a416a4 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,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>
@@ -2502,8 +2501,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..ad62671d5e5b 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>
diff --git a/core/res/res/values-lt/strings.xml b/core/res/res/values-lt/strings.xml
index 51311b379649..27a08c25a84e 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,8 @@
<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>
+ <!-- no translation found for package_updated_device_owner (7770195449213776218) -->
+ <skip />
<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>
diff --git a/core/res/res/values-lv/strings.xml b/core/res/res/values-lv/strings.xml
index 3b3609e0b07b..78c7ebdb4587 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>
diff --git a/core/res/res/values-mk/strings.xml b/core/res/res/values-mk/strings.xml
index 0c92fd5cace6..146de01e7951 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>
diff --git a/core/res/res/values-ml/strings.xml b/core/res/res/values-ml/strings.xml
index cbdbd2d8108d..3bb425ec9351 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,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>
diff --git a/core/res/res/values-mn/strings.xml b/core/res/res/values-mn/strings.xml
index 4c72a86dcc75..1ad9685a23d1 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>
@@ -2502,7 +2501,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..82f217ff21b2 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>
diff --git a/core/res/res/values-ms/strings.xml b/core/res/res/values-ms/strings.xml
index 43acbac72819..bda439a187a2 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>
diff --git a/core/res/res/values-my/strings.xml b/core/res/res/values-my/strings.xml
index 2333539d415c..9a8041644d5f 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>
diff --git a/core/res/res/values-nb/strings.xml b/core/res/res/values-nb/strings.xml
index 3a337f603348..d8e785f742d5 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>
diff --git a/core/res/res/values-ne/strings.xml b/core/res/res/values-ne/strings.xml
index ce64589a4870..88e93709453e 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>
diff --git a/core/res/res/values-nl/strings.xml b/core/res/res/values-nl/strings.xml
index f7b6cc87249b..a43c0c443c8c 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>
diff --git a/core/res/res/values-or/strings.xml b/core/res/res/values-or/strings.xml
index ef567e504e70..f61e10919209 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>
diff --git a/core/res/res/values-pa/strings.xml b/core/res/res/values-pa/strings.xml
index 7f06227ba147..5d2cc7be3039 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>
diff --git a/core/res/res/values-pl/strings.xml b/core/res/res/values-pl/strings.xml
index 73e4d24ac241..bb7edb8a79ad 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>
diff --git a/core/res/res/values-pt-rBR/strings.xml b/core/res/res/values-pt-rBR/strings.xml
index f15ab35d54f0..0440e39a9dd5 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>
diff --git a/core/res/res/values-pt-rPT/strings.xml b/core/res/res/values-pt-rPT/strings.xml
index e80b780c34cc..745e17be6c28 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,8 @@
<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>
+ <!-- no translation found for package_updated_device_owner (7770195449213776218) -->
+ <skip />
<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>
diff --git a/core/res/res/values-pt/strings.xml b/core/res/res/values-pt/strings.xml
index f15ab35d54f0..0440e39a9dd5 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>
diff --git a/core/res/res/values-ro/strings.xml b/core/res/res/values-ro/strings.xml
index f02923b611d0..4ef0423b327b 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>
diff --git a/core/res/res/values-ru/strings.xml b/core/res/res/values-ru/strings.xml
index e32ec412c4fd..a03963ef9924 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>
diff --git a/core/res/res/values-si/strings.xml b/core/res/res/values-si/strings.xml
index 35251bcd2536..571706e325c9 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>
diff --git a/core/res/res/values-sk/strings.xml b/core/res/res/values-sk/strings.xml
index 391b0d76a104..bcbd66f55412 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,8 @@
<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>
+ <!-- no translation found for package_updated_device_owner (7770195449213776218) -->
+ <skip />
<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>
diff --git a/core/res/res/values-sl/strings.xml b/core/res/res/values-sl/strings.xml
index c84fd1762e6f..c0d18ed0a067 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,8 @@
<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>
+ <!-- no translation found for package_updated_device_owner (7770195449213776218) -->
+ <skip />
<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>
diff --git a/core/res/res/values-sq/strings.xml b/core/res/res/values-sq/strings.xml
index 25d51a358278..ddccedeaa36c 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>
diff --git a/core/res/res/values-sr/strings.xml b/core/res/res/values-sr/strings.xml
index 77ceff214d60..397a39458580 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>
diff --git a/core/res/res/values-sv/strings.xml b/core/res/res/values-sv/strings.xml
index 1ee2564ea4f6..71062addb40f 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,8 @@
<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>
+ <!-- no translation found for package_updated_device_owner (7770195449213776218) -->
+ <skip />
<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>
diff --git a/core/res/res/values-sw/strings.xml b/core/res/res/values-sw/strings.xml
index 36bf76dc7eb6..e74ac1858d76 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>
diff --git a/core/res/res/values-ta/strings.xml b/core/res/res/values-ta/strings.xml
index 3fa382783be1..7737d8f6da45 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>
diff --git a/core/res/res/values-te/strings.xml b/core/res/res/values-te/strings.xml
index 027161e986fc..265fb8c9b382 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,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>
diff --git a/core/res/res/values-th/strings.xml b/core/res/res/values-th/strings.xml
index f82adc6a4f64..a922943f54c7 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,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>
@@ -2281,7 +2280,7 @@
<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>
diff --git a/core/res/res/values-tl/strings.xml b/core/res/res/values-tl/strings.xml
index 328997d33382..5e3e1e86c713 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,8 @@
<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>
+ <!-- no translation found for package_updated_device_owner (7770195449213776218) -->
+ <skip />
<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>
diff --git a/core/res/res/values-tr/strings.xml b/core/res/res/values-tr/strings.xml
index 5106e1a6545d..72d62d5b2499 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>
diff --git a/core/res/res/values-uk/strings.xml b/core/res/res/values-uk/strings.xml
index 203cdfac5c1d..e4e1e99be263 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>
diff --git a/core/res/res/values-ur/strings.xml b/core/res/res/values-ur/strings.xml
index 376efd1f0bd6..a98179a3064b 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,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>
diff --git a/core/res/res/values-uz/strings.xml b/core/res/res/values-uz/strings.xml
index a1b9e5d0a754..81ebe5fa7c2b 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>
diff --git a/core/res/res/values-vi/strings.xml b/core/res/res/values-vi/strings.xml
index 473568de3e31..4dda8cc0c8c1 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>
diff --git a/core/res/res/values-zh-rCN/strings.xml b/core/res/res/values-zh-rCN/strings.xml
index f91f457b85cc..851195acf204 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>
diff --git a/core/res/res/values-zh-rHK/strings.xml b/core/res/res/values-zh-rHK/strings.xml
index 9bcb1730d63f..87b403d39207 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>
diff --git a/core/res/res/values-zh-rTW/strings.xml b/core/res/res/values-zh-rTW/strings.xml
index 9f8fd6713097..72d2e7ce3cc1 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>
diff --git a/core/res/res/values-zu/strings.xml b/core/res/res/values-zu/strings.xml
index a0df4a1cbc0b..13356cbcd356 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>
diff --git a/core/res/res/values/attrs_manifest.xml b/core/res/res/values/attrs_manifest.xml
index 9983c459d71a..0905ae0bc155 100644
--- a/core/res/res/values/attrs_manifest.xml
+++ b/core/res/res/values/attrs_manifest.xml
@@ -854,8 +854,21 @@
<aside class="note"><b>Note:</b>
<ul>
<li>To improve the layout of apps on form factors with smallest width >= 600dp, the
- system ignores this attribute for apps that target Android 16 (API level 36) or
- higher.</li>
+ system ignores the following values of this attribute for apps that target
+ Android 16 (API level 36) or higher:
+ <ul>
+ <li><code>portrait</code></li>
+ <li><code>landscape</code></li>
+ <li><code>reversePortrait</code></li>
+ <li><code>reverseLandscape</code></li>
+ <li><code>sensorPortrait</code></li>
+ <li><code>sensorLandscape</code></li>
+ <li><code>userPortrait</code></li>
+ <li><code>userLandscape</code></li>
+ </ul>
+ <p>The values are treated as if the app had set orientation as
+ <code>unspecified</code>.</p>
+ </li>
<li>Device manufacturers can configure devices to override (ignore) this attribute
to improve the layout of apps.</li>
<li>On devices with Android 16 (API level 36) or higher installed, virtual device
diff --git a/core/res/res/values/colors.xml b/core/res/res/values/colors.xml
index 4e93c44e44bd..bf1423bda7e4 100644
--- a/core/res/res/values/colors.xml
+++ b/core/res/res/values/colors.xml
@@ -557,6 +557,7 @@
<color name="accessibility_magnification_background">#F50D60</color>
<color name="accessibility_daltonizer_background">#00BCD4</color>
<color name="accessibility_color_inversion_background">#546E7A</color>
+ <color name="accessibility_autoclick_background">#67D4FF</color>
<!-- Fullscreen magnification thumbnail color -->
<color name="accessibility_magnification_thumbnail_stroke_color">#0C0C0C</color>
diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml
index 1cb38bed1388..8c5b20da73ef 100644
--- a/core/res/res/values/config.xml
+++ b/core/res/res/values/config.xml
@@ -2812,6 +2812,20 @@
<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. -->
@@ -3072,6 +3086,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>
@@ -4422,6 +4473,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..465e318511c6 100644
--- a/core/res/res/values/dimens.xml
+++ b/core/res/res/values/dimens.xml
@@ -390,6 +390,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 +421,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 +457,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 +748,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 +915,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 -->
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 c8c1e73bfba2..3de30f7f25a8 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" />
@@ -3016,6 +3017,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" />
@@ -3266,6 +3268,8 @@
<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_progress_margin_horizontal" />
<java-symbol type="dimen" name="notification_header_background_height" />
<java-symbol type="dimen" name="notification_header_touchable_height" />
@@ -3956,6 +3960,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 +4164,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 +4446,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" />
@@ -5652,6 +5673,33 @@
<java-symbol type="id" name="accessibility_autoclick_position_button" />
<java-symbol type="drawable" name="accessibility_autoclick_pause" />
<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" />
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/app/PropertyInvalidatedCacheTests.java b/core/tests/coretests/src/android/app/PropertyInvalidatedCacheTests.java
index ac78e87aa60d..f75a72d635dd 100644
--- a/core/tests/coretests/src/android/app/PropertyInvalidatedCacheTests.java
+++ b/core/tests/coretests/src/android/app/PropertyInvalidatedCacheTests.java
@@ -414,16 +414,23 @@ public class PropertyInvalidatedCacheTests {
@Test
@DisabledOnRavenwood(reason = "SystemProperties doesn't have permission check")
public void testPermissionFailure() {
- // Create a cache that will write a system nonce.
- TestCache sysCache = new TestCache(MODULE_SYSTEM, "mode1");
try {
- // Invalidate the cache, which writes the system property. There must be a permission
- // failure.
- sysCache.invalidateCache();
- fail("expected permission failure");
- } catch (RuntimeException e) {
- // The expected exception is a bare RuntimeException. The test does not attempt to
- // validate the text of the exception message.
+ // Disable the test mode for this test, but ensure that it will be enabled when the
+ // test exits.
+ PropertyInvalidatedCache.setTestMode(false);
+ // Create a cache that will write a system nonce.
+ TestCache sysCache = new TestCache(MODULE_SYSTEM, "mode1");
+ try {
+ // Invalidate the cache, which writes the system property. There must be a
+ // permission failure.
+ sysCache.invalidateCache();
+ fail("expected permission failure");
+ } catch (RuntimeException e) {
+ // The expected exception is a bare RuntimeException. The test does not attempt
+ // to validate the text of the exception message.
+ }
+ } finally {
+ PropertyInvalidatedCache.setTestMode(true);
}
}
diff --git a/core/tests/coretests/src/android/content/pm/OWNERS b/core/tests/coretests/src/android/content/pm/OWNERS
index 867336515ce3..c4c40dcaa87a 100644
--- a/core/tests/coretests/src/android/content/pm/OWNERS
+++ b/core/tests/coretests/src/android/content/pm/OWNERS
@@ -1,5 +1,4 @@
include /core/java/android/content/pm/OWNERS
per-file AppSearchPersonTest.java = file:/core/java/android/content/pm/SHORTCUT_OWNERS
-per-file SigningDetailsTest.java = cbrubaker@google.com
per-file SigningDetailsTest.java = mpgroover@google.com
diff --git a/core/tests/coretests/src/android/content/pm/RegisteredServicesCacheTest.java b/core/tests/coretests/src/android/content/pm/RegisteredServicesCacheTest.java
index ee4761b9d024..dccbf4036b3e 100644
--- a/core/tests/coretests/src/android/content/pm/RegisteredServicesCacheTest.java
+++ b/core/tests/coretests/src/android/content/pm/RegisteredServicesCacheTest.java
@@ -245,6 +245,12 @@ public class RegisteredServicesCacheTest extends AndroidTestCase {
SERVICE_INTERFACE, SERVICE_META_DATA, ATTRIBUTES_NAME, new TestSerializer());
}
+ TestServicesCache(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) {
diff --git a/core/tests/coretests/src/android/os/IpcDataCacheTest.java b/core/tests/coretests/src/android/os/IpcDataCacheTest.java
index 74b32a17d964..791ec5d0cea3 100644
--- a/core/tests/coretests/src/android/os/IpcDataCacheTest.java
+++ b/core/tests/coretests/src/android/os/IpcDataCacheTest.java
@@ -452,22 +452,28 @@ public class IpcDataCacheTest {
assertTrue(ec.isDisabled());
}
-
// Verify that invalidating the cache from an app process would fail due to lack of permissions.
@Test
@android.platform.test.annotations.DisabledOnRavenwood(
reason = "SystemProperties doesn't have permission check")
public void testPermissionFailure() {
- // Create a cache that will write a system nonce.
- TestCache sysCache = new TestCache(IpcDataCache.MODULE_SYSTEM, "mode1");
try {
- // Invalidate the cache, which writes the system property. There must be a permission
- // failure.
- sysCache.invalidateCache();
- fail("expected permission failure");
- } catch (RuntimeException e) {
- // The expected exception is a bare RuntimeException. The test does not attempt to
- // validate the text of the exception message.
+ // Disable test mode for this test. Guarantee that the mode is enabled before the
+ // test exits.
+ IpcDataCache.setTestMode(false);
+ // Create a cache that will write a system nonce.
+ TestCache sysCache = new TestCache(IpcDataCache.MODULE_SYSTEM, "mode1");
+ try {
+ // Invalidate the cache, which writes the system property. There must be a
+ // permission failure.
+ sysCache.invalidateCache();
+ fail("expected permission failure");
+ } catch (RuntimeException e) {
+ // The expected exception is a bare RuntimeException. The test does not attempt
+ // to validate the text of the exception message.
+ }
+ } finally {
+ IpcDataCache.setTestMode(true);
}
}
diff --git a/core/tests/coretests/src/android/os/PerfettoTraceTest.java b/core/tests/coretests/src/android/os/PerfettoTraceTest.java
index 69150150d6f9..790ac4a55dc6 100644
--- a/core/tests/coretests/src/android/os/PerfettoTraceTest.java
+++ b/core/tests/coretests/src/android/os/PerfettoTraceTest.java
@@ -33,6 +33,7 @@ import androidx.test.InstrumentationRegistry;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import org.junit.Before;
+import org.junit.Ignore;
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -611,6 +612,7 @@ public class PerfettoTraceTest {
@Test
@RequiresFlagsEnabled(android.os.Flags.FLAG_PERFETTO_SDK_TRACING_V2)
+ @Ignore("b/303199244")
public void testMessageQueue() throws Exception {
TraceConfig traceConfig = getTraceConfig("mq");
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/ViewRootImplTest.java b/core/tests/coretests/src/android/view/ViewRootImplTest.java
index f5d1e7a85e83..39f3d3319ad3 100644
--- a/core/tests/coretests/src/android/view/ViewRootImplTest.java
+++ b/core/tests/coretests/src/android/view/ViewRootImplTest.java
@@ -16,6 +16,8 @@
package android.view;
+import static android.app.UiModeManager.MODE_NIGHT_NO;
+import static android.app.UiModeManager.MODE_NIGHT_YES;
import static android.util.SequenceUtils.getInitSeq;
import static android.view.HapticFeedbackConstants.FLAG_IGNORE_GLOBAL_SETTING;
import static android.view.InputDevice.SOURCE_ROTARY_ENCODER;
@@ -67,8 +69,10 @@ import static org.junit.Assume.assumeTrue;
import android.annotation.NonNull;
import android.app.Instrumentation;
import android.app.UiModeManager;
+import android.app.UiModeManager.ForceInvertType;
import android.content.Context;
import android.graphics.ForceDarkType;
+import android.graphics.ForceDarkType.ForceDarkTypeDef;
import android.graphics.Rect;
import android.hardware.display.DisplayManagerGlobal;
import android.os.Binder;
@@ -93,9 +97,12 @@ import androidx.test.filters.SmallTest;
import androidx.test.platform.app.InstrumentationRegistry;
import com.android.compatibility.common.util.ShellIdentityUtils;
+import com.android.compatibility.common.util.TestUtils;
import com.android.cts.input.BlockingQueueEventVerifier;
import com.android.window.flags.Flags;
+import com.google.common.truth.Expect;
+
import org.hamcrest.Matcher;
import org.junit.After;
import org.junit.AfterClass;
@@ -124,6 +131,8 @@ public class ViewRootImplTest {
@Rule
public final SetFlagsRule mSetFlagsRule = new SetFlagsRule();
+ @Rule
+ public final Expect mExpect = Expect.create();
private ViewRootImpl mViewRootImpl;
private View mView;
@@ -1507,49 +1516,34 @@ public class ViewRootImplTest {
}
@Test
- public void forceInvertOffDarkThemeOff_forceDarkModeDisabled() {
- mSetFlagsRule.enableFlags(FLAG_FORCE_INVERT_COLOR);
- ShellIdentityUtils.invokeWithShellPermissions(() -> {
- Settings.Secure.putInt(
- sContext.getContentResolver(),
- Settings.Secure.ACCESSIBILITY_FORCE_INVERT_COLOR_ENABLED,
- /* value= */ 0
- );
- var uiModeManager = sContext.getSystemService(UiModeManager.class);
- uiModeManager.setNightMode(UiModeManager.MODE_NIGHT_NO);
- });
-
- sInstrumentation.runOnMainSync(() ->
- mViewRootImpl.updateConfiguration(sContext.getDisplayNoVerify().getDisplayId())
- );
-
- assertThat(mViewRootImpl.determineForceDarkType()).isEqualTo(ForceDarkType.NONE);
- }
-
- @Test
- public void forceInvertOnDarkThemeOff_forceDarkModeEnabled() {
- mSetFlagsRule.enableFlags(FLAG_FORCE_INVERT_COLOR);
- ShellIdentityUtils.invokeWithShellPermissions(() -> {
- Settings.Secure.putInt(
- sContext.getContentResolver(),
- Settings.Secure.ACCESSIBILITY_FORCE_INVERT_COLOR_ENABLED,
- /* value= */ 1
- );
- var uiModeManager = sContext.getSystemService(UiModeManager.class);
- uiModeManager.setNightMode(UiModeManager.MODE_NIGHT_NO);
- });
-
- sInstrumentation.runOnMainSync(() ->
- mViewRootImpl.updateConfiguration(sContext.getDisplayNoVerify().getDisplayId())
- );
-
- assertThat(mViewRootImpl.determineForceDarkType())
- .isEqualTo(ForceDarkType.FORCE_INVERT_COLOR_DARK);
+ @RequiresFlagsEnabled(FLAG_FORCE_INVERT_COLOR)
+ public void updateConfiguration_returnsExpectedForceDarkMode() {
+ waitForSystemNightModeActivated(true);
+
+ verifyForceDarkType(/* isAppInNightMode= */ true, /* isForceInvertEnabled= */ true,
+ UiModeManager.FORCE_INVERT_TYPE_DARK, ForceDarkType.FORCE_INVERT_COLOR_DARK);
+ verifyForceDarkType(/* isAppInNightMode= */ true, /* isForceInvertEnabled= */ false,
+ UiModeManager.FORCE_INVERT_TYPE_OFF, ForceDarkType.NONE);
+ verifyForceDarkType(/* isAppInNightMode= */ false, /* isForceInvertEnabled= */ true,
+ UiModeManager.FORCE_INVERT_TYPE_DARK, ForceDarkType.FORCE_INVERT_COLOR_DARK);
+ verifyForceDarkType(/* isAppInNightMode= */ false, /* isForceInvertEnabled= */ false,
+ UiModeManager.FORCE_INVERT_TYPE_OFF, ForceDarkType.NONE);
+
+ waitForSystemNightModeActivated(false);
+
+ verifyForceDarkType(/* isAppInNightMode= */ true, /* isForceInvertEnabled= */ true,
+ UiModeManager.FORCE_INVERT_TYPE_OFF, ForceDarkType.NONE);
+ verifyForceDarkType(/* isAppInNightMode= */ true, /* isForceInvertEnabled= */ false,
+ UiModeManager.FORCE_INVERT_TYPE_OFF, ForceDarkType.NONE);
+ verifyForceDarkType(/* isAppInNightMode= */ false, /* isForceInvertEnabled= */ true,
+ UiModeManager.FORCE_INVERT_TYPE_OFF, ForceDarkType.NONE);
+ verifyForceDarkType(/* isAppInNightMode= */ false, /* isForceInvertEnabled= */ false,
+ UiModeManager.FORCE_INVERT_TYPE_OFF, ForceDarkType.NONE);
}
@Test
+ @EnableFlags(FLAG_FORCE_INVERT_COLOR)
public void forceInvertOffForceDarkOff_forceDarkModeDisabled() {
- mSetFlagsRule.enableFlags(FLAG_FORCE_INVERT_COLOR);
ShellIdentityUtils.invokeWithShellPermissions(() -> {
Settings.Secure.putInt(
sContext.getContentResolver(),
@@ -1562,15 +1556,14 @@ public class ViewRootImplTest {
});
sInstrumentation.runOnMainSync(() ->
- mViewRootImpl.updateConfiguration(sContext.getDisplayNoVerify().getDisplayId())
- );
+ mViewRootImpl.updateConfiguration(sContext.getDisplayNoVerify().getDisplayId()));
assertThat(mViewRootImpl.determineForceDarkType()).isEqualTo(ForceDarkType.NONE);
}
@Test
+ @EnableFlags(FLAG_FORCE_INVERT_COLOR)
public void forceInvertOffForceDarkOn_forceDarkModeEnabled() {
- mSetFlagsRule.enableFlags(FLAG_FORCE_INVERT_COLOR);
ShellIdentityUtils.invokeWithShellPermissions(() -> {
Settings.Secure.putInt(
sContext.getContentResolver(),
@@ -1582,8 +1575,7 @@ public class ViewRootImplTest {
});
sInstrumentation.runOnMainSync(() ->
- mViewRootImpl.updateConfiguration(sContext.getDisplayNoVerify().getDisplayId())
- );
+ mViewRootImpl.updateConfiguration(sContext.getDisplayNoVerify().getDisplayId()));
assertThat(mViewRootImpl.determineForceDarkType()).isEqualTo(ForceDarkType.FORCE_DARK);
}
@@ -1790,4 +1782,39 @@ public class ViewRootImplTest {
() -> view.getViewTreeObserver().removeOnDrawListener(listener));
}
}
+
+ private void waitForSystemNightModeActivated(boolean active) {
+ ShellIdentityUtils.invokeWithShellPermissions(() ->
+ sInstrumentation.runOnMainSync(() -> {
+ var uiModeManager = sContext.getSystemService(UiModeManager.class);
+ uiModeManager.setNightModeActivated(active);
+ }));
+ sInstrumentation.waitForIdleSync();
+ }
+
+ private void verifyForceDarkType(boolean isAppInNightMode, boolean isForceInvertEnabled,
+ @ForceInvertType int expectedForceInvertType,
+ @ForceDarkTypeDef int expectedForceDarkType) {
+ var uiModeManager = sContext.getSystemService(UiModeManager.class);
+ ShellIdentityUtils.invokeWithShellPermissions(() -> {
+ uiModeManager.setApplicationNightMode(
+ isAppInNightMode ? MODE_NIGHT_YES : MODE_NIGHT_NO);
+ Settings.Secure.putInt(
+ sContext.getContentResolver(),
+ Settings.Secure.ACCESSIBILITY_FORCE_INVERT_COLOR_ENABLED,
+ isForceInvertEnabled ? 1 : 0);
+ });
+
+ sInstrumentation.runOnMainSync(() ->
+ mViewRootImpl.updateConfiguration(sContext.getDisplayNoVerify().getDisplayId()));
+ try {
+ TestUtils.waitUntil("Waiting for force invert state changed",
+ () -> (uiModeManager.getForceInvertState() == expectedForceInvertType));
+ } catch (Exception e) {
+ Log.e(TAG, "Unexpected error trying to apply force invert state. " + e);
+ e.printStackTrace();
+ }
+
+ mExpect.that(mViewRootImpl.determineForceDarkType()).isEqualTo(expectedForceDarkType);
+ }
}
diff --git a/core/tests/coretests/src/android/window/WindowOnBackInvokedDispatcherTest.java b/core/tests/coretests/src/android/window/WindowOnBackInvokedDispatcherTest.java
index 4d6c30ebbe2b..215c1623a530 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);
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/os/DebugStoreTest.java b/core/tests/coretests/src/com/android/internal/os/DebugStoreTest.java
index 786c2fc63018..bc452e7689e0 100644
--- a/core/tests/coretests/src/com/android/internal/os/DebugStoreTest.java
+++ b/core/tests/coretests/src/com/android/internal/os/DebugStoreTest.java
@@ -47,21 +47,17 @@ import java.util.List;
/**
* Test class for {@link DebugStore}.
*
- * To run it:
- * atest FrameworksCoreTests:com.android.internal.os.DebugStoreTest
+ * <p>To run it: atest FrameworksCoreTests:com.android.internal.os.DebugStoreTest
*/
@RunWith(AndroidJUnit4.class)
@DisabledOnRavenwood(blockedBy = DebugStore.class)
@SmallTest
public class DebugStoreTest {
- @Rule
- public final RavenwoodRule mRavenwood = new RavenwoodRule();
+ @Rule public final RavenwoodRule mRavenwood = new RavenwoodRule();
- @Mock
- private DebugStore.DebugStoreNative mDebugStoreNativeMock;
+ @Mock private DebugStore.DebugStoreNative mDebugStoreNativeMock;
- @Captor
- private ArgumentCaptor<List<String>> mListCaptor;
+ @Captor private ArgumentCaptor<List<String>> mListCaptor;
@Before
public void setUp() {
@@ -79,16 +75,14 @@ public class DebugStoreTest {
when(mDebugStoreNativeMock.beginEvent(anyString(), anyList())).thenReturn(1L);
long eventId = DebugStore.recordServiceOnStart(1, 0, intent);
-
- verify(mDebugStoreNativeMock).beginEvent(eq("SvcStart"), mListCaptor.capture());
- List<String> capturedList = mListCaptor.getValue();
- assertThat(capturedList).containsExactly(
- "stId", "1",
- "flg", "0",
- "act", "com.android.ACTION",
- "comp", "ComponentInfo{com.android/androidService}",
- "pkg", "com.android"
- ).inOrder();
+ assertThat(paramsForBeginEvent("SvcStart"))
+ .containsExactly(
+ "stId", "1",
+ "flg", "0",
+ "act", "com.android.ACTION",
+ "comp", "ComponentInfo{com.android/androidService}",
+ "pkg", "com.android")
+ .inOrder();
assertThat(eventId).isEqualTo(1L);
}
@@ -101,13 +95,11 @@ public class DebugStoreTest {
when(mDebugStoreNativeMock.beginEvent(anyString(), anyList())).thenReturn(2L);
long eventId = DebugStore.recordServiceCreate(serviceInfo);
-
- verify(mDebugStoreNativeMock).beginEvent(eq("SvcCreate"), mListCaptor.capture());
- List<String> capturedList = mListCaptor.getValue();
- assertThat(capturedList).containsExactly(
- "name", "androidService",
- "pkg", "com.android"
- ).inOrder();
+ assertThat(paramsForBeginEvent("SvcCreate"))
+ .containsExactly(
+ "name", "androidService",
+ "pkg", "com.android")
+ .inOrder();
assertThat(eventId).isEqualTo(2L);
}
@@ -121,59 +113,60 @@ public class DebugStoreTest {
when(mDebugStoreNativeMock.beginEvent(anyString(), anyList())).thenReturn(3L);
long eventId = DebugStore.recordServiceBind(true, intent);
-
- verify(mDebugStoreNativeMock).beginEvent(eq("SvcBind"), mListCaptor.capture());
- List<String> capturedList = mListCaptor.getValue();
- assertThat(capturedList).containsExactly(
- "rebind", "true",
- "act", "com.android.ACTION",
- "cmp", "ComponentInfo{com.android/androidService}",
- "pkg", "com.android"
- ).inOrder();
+ assertThat(paramsForBeginEvent("SvcBind"))
+ .containsExactly(
+ "rebind", "true",
+ "act", "com.android.ACTION",
+ "cmp", "ComponentInfo{com.android/androidService}",
+ "pkg", "com.android")
+ .inOrder();
assertThat(eventId).isEqualTo(3L);
}
@Test
public void testRecordGoAsync() {
- DebugStore.recordGoAsync("androidReceiver");
-
- verify(mDebugStoreNativeMock).recordEvent(eq("GoAsync"), mListCaptor.capture());
- List<String> capturedList = mListCaptor.getValue();
- assertThat(capturedList).containsExactly(
- "tname", Thread.currentThread().getName(),
- "tid", String.valueOf(Thread.currentThread().getId()),
- "rcv", "androidReceiver"
- ).inOrder();
+ DebugStore.recordGoAsync(3840 /* 0xf00 */);
+
+ assertThat(paramsForRecordEvent("GoAsync"))
+ .containsExactly(
+ "tname",
+ Thread.currentThread().getName(),
+ "tid",
+ String.valueOf(Thread.currentThread().getId()),
+ "prid",
+ "f00")
+ .inOrder();
}
@Test
public void testRecordFinish() {
- DebugStore.recordFinish("androidReceiver");
-
- verify(mDebugStoreNativeMock).recordEvent(eq("Finish"), mListCaptor.capture());
- List<String> capturedList = mListCaptor.getValue();
- assertThat(capturedList).containsExactly(
- "tname", Thread.currentThread().getName(),
- "tid", String.valueOf(Thread.currentThread().getId()),
- "rcv", "androidReceiver"
- ).inOrder();
+ DebugStore.recordFinish(3840 /* 0xf00 */);
+
+ assertThat(paramsForRecordEvent("Finish"))
+ .containsExactly(
+ "tname",
+ Thread.currentThread().getName(),
+ "tid",
+ String.valueOf(Thread.currentThread().getId()),
+ "prid",
+ "f00")
+ .inOrder();
}
@Test
public void testRecordLongLooperMessage() {
DebugStore.recordLongLooperMessage(100, "androidHandler", 500L);
- verify(mDebugStoreNativeMock).recordEvent(eq("LooperMsg"), mListCaptor.capture());
- List<String> capturedList = mListCaptor.getValue();
- assertThat(capturedList).containsExactly(
- "code", "100",
- "trgt", "androidHandler",
- "elapsed", "500"
- ).inOrder();
+ assertThat(paramsForRecordEvent("LooperMsg"))
+ .containsExactly(
+ "code", "100",
+ "trgt", "androidHandler",
+ "elapsed", "500")
+ .inOrder();
}
@Test
- public void testRecordBroadcastHandleReceiver() {
+ public void testRecordBroadcastReceive() {
Intent intent = new Intent();
intent.setAction("com.android.ACTION");
intent.setComponent(new ComponentName("com.android", "androidReceiver"));
@@ -181,21 +174,87 @@ public class DebugStoreTest {
when(mDebugStoreNativeMock.beginEvent(anyString(), anyList())).thenReturn(4L);
- long eventId = DebugStore.recordBroadcastHandleReceiver(intent);
-
- verify(mDebugStoreNativeMock).beginEvent(eq("HandleReceiver"), mListCaptor.capture());
- List<String> capturedList = mListCaptor.getValue();
- assertThat(capturedList).containsExactly(
- "tname", Thread.currentThread().getName(),
- "tid", String.valueOf(Thread.currentThread().getId()),
- "act", "com.android.ACTION",
- "cmp", "ComponentInfo{com.android/androidReceiver}",
- "pkg", "com.android"
- ).inOrder();
+ long eventId = DebugStore.recordBroadcastReceive(intent, 3840 /* 0xf00 */);
+ assertThat(paramsForBeginEvent("BcRcv"))
+ .containsExactly(
+ "tname", Thread.currentThread().getName(),
+ "tid", String.valueOf(Thread.currentThread().getId()),
+ "act", "com.android.ACTION",
+ "cmp", "ComponentInfo{com.android/androidReceiver}",
+ "pkg", "com.android",
+ "prid", "f00")
+ .inOrder();
assertThat(eventId).isEqualTo(4L);
}
@Test
+ public void testRecordBroadcastReceiveReg() {
+ Intent intent = new Intent();
+ intent.setAction("com.android.ACTION");
+ intent.setComponent(new ComponentName("com.android", "androidReceiver"));
+ intent.setPackage("com.android");
+
+ when(mDebugStoreNativeMock.beginEvent(anyString(), anyList())).thenReturn(5L);
+
+ long eventId = DebugStore.recordBroadcastReceiveReg(intent, 3840 /* 0xf00 */);
+ assertThat(paramsForBeginEvent("BcRcvReg"))
+ .containsExactly(
+ "tname",
+ Thread.currentThread().getName(),
+ "tid",
+ String.valueOf(Thread.currentThread().getId()),
+ "act",
+ "com.android.ACTION",
+ "cmp",
+ "ComponentInfo{com.android/androidReceiver}",
+ "pkg",
+ "com.android",
+ "prid",
+ "f00")
+ .inOrder();
+ assertThat(eventId).isEqualTo(5L);
+ }
+
+ @Test
+ public void testRecordHandleBindApplication() {
+ when(mDebugStoreNativeMock.beginEvent(anyString(), anyList())).thenReturn(6L);
+ long eventId = DebugStore.recordHandleBindApplication();
+
+ assertThat(paramsForBeginEvent("BindApp")).isEmpty();
+ assertThat(eventId).isEqualTo(6L);
+ }
+
+ @Test
+ public void testRecordScheduleReceiver() {
+ when(mDebugStoreNativeMock.beginEvent(anyString(), anyList())).thenReturn(7L);
+ long eventId = DebugStore.recordScheduleReceiver();
+
+ assertThat(paramsForBeginEvent("SchRcv"))
+ .containsExactly(
+ "tname",
+ Thread.currentThread().getName(),
+ "tid",
+ String.valueOf(Thread.currentThread().getId()))
+ .inOrder();
+ assertThat(eventId).isEqualTo(7L);
+ }
+
+ @Test
+ public void testRecordScheduleRegisteredReceiver() {
+ when(mDebugStoreNativeMock.beginEvent(anyString(), anyList())).thenReturn(8L);
+ long eventId = DebugStore.recordScheduleRegisteredReceiver();
+
+ assertThat(paramsForBeginEvent("SchRcvReg"))
+ .containsExactly(
+ "tname",
+ Thread.currentThread().getName(),
+ "tid",
+ String.valueOf(Thread.currentThread().getId()))
+ .inOrder();
+ assertThat(eventId).isEqualTo(8L);
+ }
+
+ @Test
public void testRecordEventEnd() {
DebugStore.recordEventEnd(1L);
@@ -203,109 +262,124 @@ public class DebugStoreTest {
}
@Test
- public void testRecordServiceOnStartWithNullIntent() {
+ public void testRecordServiceOnStart_withNullIntent() {
when(mDebugStoreNativeMock.beginEvent(anyString(), anyList())).thenReturn(5L);
long eventId = DebugStore.recordServiceOnStart(1, 0, null);
-
- verify(mDebugStoreNativeMock).beginEvent(eq("SvcStart"), mListCaptor.capture());
- List<String> capturedList = mListCaptor.getValue();
- assertThat(capturedList).containsExactly(
- "stId", "1",
- "flg", "0",
- "act", "null",
- "comp", "null",
- "pkg", "null"
- ).inOrder();
+ assertThat(paramsForBeginEvent("SvcStart"))
+ .containsExactly(
+ "stId", "1",
+ "flg", "0",
+ "act", "null",
+ "comp", "null",
+ "pkg", "null")
+ .inOrder();
assertThat(eventId).isEqualTo(5L);
}
@Test
- public void testRecordServiceCreateWithNullServiceInfo() {
+ public void testRecordServiceCreate_withNullServiceInfo() {
when(mDebugStoreNativeMock.beginEvent(anyString(), anyList())).thenReturn(6L);
long eventId = DebugStore.recordServiceCreate(null);
-
- verify(mDebugStoreNativeMock).beginEvent(eq("SvcCreate"), mListCaptor.capture());
- List<String> capturedList = mListCaptor.getValue();
- assertThat(capturedList).containsExactly(
- "name", "null",
- "pkg", "null"
- ).inOrder();
+ assertThat(paramsForBeginEvent("SvcCreate"))
+ .containsExactly(
+ "name", "null",
+ "pkg", "null")
+ .inOrder();
assertThat(eventId).isEqualTo(6L);
}
@Test
- public void testRecordServiceBindWithNullIntent() {
+ public void testRecordServiceBind_withNullIntent() {
when(mDebugStoreNativeMock.beginEvent(anyString(), anyList())).thenReturn(7L);
long eventId = DebugStore.recordServiceBind(false, null);
-
- verify(mDebugStoreNativeMock).beginEvent(eq("SvcBind"), mListCaptor.capture());
- List<String> capturedList = mListCaptor.getValue();
- assertThat(capturedList).containsExactly(
- "rebind", "false",
- "act", "null",
- "cmp", "null",
- "pkg", "null"
- ).inOrder();
+ assertThat(paramsForBeginEvent("SvcBind"))
+ .containsExactly(
+ "rebind", "false",
+ "act", "null",
+ "cmp", "null",
+ "pkg", "null")
+ .inOrder();
assertThat(eventId).isEqualTo(7L);
}
@Test
- public void testRecordBroadcastHandleReceiverWithNullIntent() {
+ public void testRecordBroadcastReceive_withNullIntent() {
when(mDebugStoreNativeMock.beginEvent(anyString(), anyList())).thenReturn(8L);
- long eventId = DebugStore.recordBroadcastHandleReceiver(null);
-
- verify(mDebugStoreNativeMock).beginEvent(eq("HandleReceiver"), mListCaptor.capture());
- List<String> capturedList = mListCaptor.getValue();
- assertThat(capturedList).containsExactly(
- "tname", Thread.currentThread().getName(),
- "tid", String.valueOf(Thread.currentThread().getId()),
- "act", "null",
- "cmp", "null",
- "pkg", "null"
- ).inOrder();
+ long eventId = DebugStore.recordBroadcastReceive(null, 3840 /* 0xf00 */);
+ assertThat(paramsForBeginEvent("BcRcv"))
+ .containsExactly(
+ "tname", Thread.currentThread().getName(),
+ "tid", String.valueOf(Thread.currentThread().getId()),
+ "act", "null",
+ "cmp", "null",
+ "pkg", "null",
+ "prid", "f00")
+ .inOrder();
assertThat(eventId).isEqualTo(8L);
}
@Test
- public void testRecordGoAsyncWithNullReceiverClassName() {
- DebugStore.recordGoAsync(null);
-
- verify(mDebugStoreNativeMock).recordEvent(eq("GoAsync"), mListCaptor.capture());
- List<String> capturedList = mListCaptor.getValue();
- assertThat(capturedList).containsExactly(
- "tname", Thread.currentThread().getName(),
- "tid", String.valueOf(Thread.currentThread().getId()),
- "rcv", "null"
- ).inOrder();
+ public void testRecordBroadcastReceiveReg_withNullIntent() {
+ when(mDebugStoreNativeMock.beginEvent(anyString(), anyList())).thenReturn(8L);
+
+ long eventId = DebugStore.recordBroadcastReceiveReg(null, 3840 /* 0xf00 */);
+ assertThat(paramsForBeginEvent("BcRcvReg"))
+ .containsExactly(
+ "tname",
+ Thread.currentThread().getName(),
+ "tid",
+ String.valueOf(Thread.currentThread().getId()),
+ "act",
+ "null",
+ "cmp",
+ "null",
+ "pkg",
+ "null",
+ "prid",
+ "f00")
+ .inOrder();
+ assertThat(eventId).isEqualTo(8L);
}
@Test
- public void testRecordFinishWithNullReceiverClassName() {
- DebugStore.recordFinish(null);
-
- verify(mDebugStoreNativeMock).recordEvent(eq("Finish"), mListCaptor.capture());
- List<String> capturedList = mListCaptor.getValue();
- assertThat(capturedList).containsExactly(
- "tname", Thread.currentThread().getName(),
- "tid", String.valueOf(Thread.currentThread().getId()),
- "rcv", "null"
- ).inOrder();
+ public void testRecordFinish_withNullReceiverClassName() {
+ DebugStore.recordFinish(3840 /* 0xf00 */);
+
+ assertThat(paramsForRecordEvent("Finish"))
+ .containsExactly(
+ "tname",
+ Thread.currentThread().getName(),
+ "tid",
+ String.valueOf(Thread.currentThread().getId()),
+ "prid",
+ "f00")
+ .inOrder();
}
@Test
- public void testRecordLongLooperMessageWithNullTargetClass() {
+ public void testRecordLongLooperMessage_withNullTargetClass() {
DebugStore.recordLongLooperMessage(200, null, 1000L);
- verify(mDebugStoreNativeMock).recordEvent(eq("LooperMsg"), mListCaptor.capture());
- List<String> capturedList = mListCaptor.getValue();
- assertThat(capturedList).containsExactly(
- "code", "200",
- "trgt", "null",
- "elapsed", "1000"
- ).inOrder();
+ assertThat(paramsForRecordEvent("LooperMsg"))
+ .containsExactly(
+ "code", "200",
+ "trgt", "null",
+ "elapsed", "1000")
+ .inOrder();
}
+
+ private List<String> paramsForBeginEvent(String eventName) {
+ verify(mDebugStoreNativeMock).beginEvent(eq(eventName), mListCaptor.capture());
+ return mListCaptor.getValue();
+ }
+
+ private List<String> paramsForRecordEvent(String eventName) {
+ verify(mDebugStoreNativeMock).recordEvent(eq(eventName), mListCaptor.capture());
+ return mListCaptor.getValue();
+ }
+
}
diff --git a/core/tests/coretests/src/com/android/internal/os/OWNERS b/core/tests/coretests/src/com/android/internal/os/OWNERS
index 3f8f9e29c6a0..ec236ca7fb32 100644
--- a/core/tests/coretests/src/com/android/internal/os/OWNERS
+++ b/core/tests/coretests/src/com/android/internal/os/OWNERS
@@ -2,3 +2,4 @@ include /BATTERY_STATS_OWNERS
# CPU
per-file *Cpu* = file:/core/java/com/android/internal/os/CPU_OWNERS
+per-file *DebugStore* = file:/core/java/com/android/internal/os/DEBUG_STORE_OWNERS
diff --git a/core/tests/featureflagtests/OWNERS b/core/tests/featureflagtests/OWNERS
index 2ff4f5ab8807..6784f2891009 100644
--- a/core/tests/featureflagtests/OWNERS
+++ b/core/tests/featureflagtests/OWNERS
@@ -1,2 +1 @@
-sbasi@google.com
-tmfang@google.com \ No newline at end of file
+tmfang@google.com
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/data/etc/OWNERS b/data/etc/OWNERS
index 712042f6ff6b..1251fce1ce19 100644
--- a/data/etc/OWNERS
+++ b/data/etc/OWNERS
@@ -1,6 +1,5 @@
include /PACKAGE_MANAGER_OWNERS
-cbrubaker@google.com
hackbod@android.com
hackbod@google.com
jeffv@google.com
diff --git a/data/etc/privapp-permissions-platform.xml b/data/etc/privapp-permissions-platform.xml
index 15f70298198f..9234902335c1 100644
--- a/data/etc/privapp-permissions-platform.xml
+++ b/data/etc/privapp-permissions-platform.xml
@@ -619,6 +619,8 @@ applications that come with the platform
<permission name="android.permission.MANAGE_GLOBAL_PICTURE_QUALITY_SERVICE"/>
<permission name="android.permission.MANAGE_GLOBAL_SOUND_QUALITY_SERVICE"/>
<permission name="android.permission.READ_COLOR_ZONES"/>
+ <!-- Permission required for CTS test - CtsTextClassifierTestCases -->
+ <permission name="android.permission.ACCESS_TEXT_CLASSIFIER_BY_TYPE"/>
</privapp-permissions>
<privapp-permissions package="com.android.statementservice">
diff --git a/graphics/java/android/graphics/Paint.java b/graphics/java/android/graphics/Paint.java
index a0ca0988e03c..05e23a08481f 100644
--- a/graphics/java/android/graphics/Paint.java
+++ b/graphics/java/android/graphics/Paint.java
@@ -2206,26 +2206,21 @@ public class Paint {
* @param fontVariationOverride font variation override. You can pass null or empty string for
* clearing font variation override.
*
- * @return true if the provided font variation settings is valid. Otherwise returns false.
- *
+ * @throws IllegalArgumentException If given string is not a valid font variation settings
+ * format
* @see #getFontVariationSettings()
* @see #setFontVariationSettings(String)
* @see #getFontVariationOverride()
* @see FontVariationAxis
*/
@FlaggedApi(FLAG_TYPEFACE_REDESIGN_READONLY)
- public boolean setFontVariationOverride(@Nullable String fontVariationOverride) {
+ public void setFontVariationOverride(@Nullable String fontVariationOverride) {
if (Objects.equals(fontVariationOverride, mFontVariationOverride)) {
- return true;
+ return;
}
- List<FontVariationAxis> axes;
- try {
- axes = FontVariationAxis.fromFontVariationSettingsForList(fontVariationOverride);
- } catch (IllegalArgumentException e) {
- Log.i(TAG, "failed to parse font variation settings.", e);
- return false;
- }
+ List<FontVariationAxis> axes =
+ FontVariationAxis.fromFontVariationSettingsForList(fontVariationOverride);
long builderPtr = nCreateFontVariationBuilder(axes.size());
for (int i = 0; i < axes.size(); ++i) {
FontVariationAxis axis = axes.get(i);
@@ -2234,7 +2229,6 @@ public class Paint {
}
nSetFontVariationOverride(mNativePaint, builderPtr);
mFontVariationOverride = fontVariationOverride;
- return true;
}
/**
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/multitasking.aconfig b/libs/WindowManager/Shell/aconfig/multitasking.aconfig
index 1e72d64397d7..ab2804626361 100644
--- a/libs/WindowManager/Shell/aconfig/multitasking.aconfig
+++ b/libs/WindowManager/Shell/aconfig/multitasking.aconfig
@@ -194,3 +194,13 @@ flag {
purpose: PURPOSE_BUGFIX
}
}
+
+flag {
+ name: "enable_gsf"
+ namespace: "multitasking"
+ description: "Applies GSF font styles to multitasking."
+ bug: "400534660"
+ metadata {
+ purpose: PURPOSE_BUGFIX
+ }
+} \ No newline at end of file
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/res/layout/desktop_mode_window_decor_handle_menu.xml b/libs/WindowManager/Shell/res/layout/desktop_mode_window_decor_handle_menu.xml
index 477d207a5c7e..16e098b39004 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
@@ -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/values-af/strings.xml b/libs/WindowManager/Shell/res/values-af/strings.xml
index 4dffce59aec6..61dbb69761ad 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>
@@ -115,14 +113,10 @@
<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>
@@ -158,14 +152,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..52008a6ff952 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>
@@ -115,14 +113,10 @@
<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>
@@ -158,14 +152,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..65dd965afbf1 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>
@@ -115,14 +113,10 @@
<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>
@@ -158,14 +152,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..919447e125a1 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>
@@ -115,14 +113,10 @@
<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>
@@ -158,14 +152,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..846240f80037 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>
@@ -115,14 +113,10 @@
<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>
@@ -158,14 +152,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..4ee7d7ee92bb 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>
@@ -115,14 +113,10 @@
<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>
@@ -158,14 +152,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..d686da96d3d8 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>
@@ -115,14 +113,10 @@
<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>
@@ -158,14 +152,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..984c20ff5dba 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>
@@ -115,14 +113,10 @@
<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>
@@ -158,14 +152,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..dd5653c48464 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>
@@ -115,14 +113,10 @@
<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>
@@ -158,14 +152,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..01e0515dfa16 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>
@@ -115,14 +113,10 @@
<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>
@@ -158,14 +152,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..77cf94b84450 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>
@@ -115,14 +113,10 @@
<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>
@@ -158,14 +152,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..1a414ba89fdc 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>
@@ -115,14 +113,10 @@
<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>
@@ -158,14 +152,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..07f1969ec9c6 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>
@@ -115,14 +113,10 @@
<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>
@@ -158,14 +152,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..7a8f2d984457 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>
@@ -115,14 +113,10 @@
<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>
@@ -158,14 +152,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..e2477d35881c 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>
@@ -115,14 +113,10 @@
<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>
@@ -158,14 +152,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..bed6e5058a9f 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>
@@ -115,14 +113,10 @@
<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>
@@ -158,14 +152,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-rGB/strings.xml b/libs/WindowManager/Shell/res/values-en-rGB/strings.xml
index b137d80dcd2b..bed6e5058a9f 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>
@@ -115,14 +113,10 @@
<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>
@@ -158,14 +152,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..bed6e5058a9f 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>
@@ -115,14 +113,10 @@
<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>
@@ -158,14 +152,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..2b7769d19168 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>
@@ -115,14 +113,10 @@
<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>
@@ -142,7 +136,7 @@
<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>
<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 +152,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..afb1034551b3 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>
@@ -115,14 +113,10 @@
<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>
@@ -158,14 +152,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..61ca3e0db920 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>
@@ -115,14 +113,10 @@
<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>
@@ -158,14 +152,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..af175bc9d08e 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>
@@ -115,14 +113,10 @@
<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>
@@ -158,14 +152,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..4df47c1dd2cf 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>
@@ -115,14 +113,10 @@
<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>
@@ -158,14 +152,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..7da06bd50378 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>
@@ -115,14 +113,10 @@
<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>
@@ -158,14 +152,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..d0fac37dc8ed 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>
@@ -115,14 +113,10 @@
<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>
@@ -158,14 +152,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..91dc6de3a27f 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>
@@ -115,14 +113,10 @@
<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>
@@ -158,14 +152,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..e315648c598d 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>
@@ -115,14 +113,10 @@
<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>
@@ -158,14 +152,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-hi/strings.xml b/libs/WindowManager/Shell/res/values-hi/strings.xml
index d0be7d560961..cbcb2674c6b4 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>
@@ -115,14 +113,10 @@
<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>
@@ -158,14 +152,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..0eed0a422cf1 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>
@@ -115,14 +113,10 @@
<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>
@@ -158,14 +152,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..48d5afd778c3 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>
@@ -115,14 +113,10 @@
<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>
@@ -158,14 +152,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..bf1e840179d5 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>
@@ -115,14 +113,10 @@
<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>
@@ -158,14 +152,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..d658f59ec9e3 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>
@@ -115,14 +113,10 @@
<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>
@@ -158,14 +152,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..f7b78d2222f5 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>
@@ -115,14 +113,10 @@
<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>
@@ -158,14 +152,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..a2787a7e7b91 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>
@@ -115,14 +113,10 @@
<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>
@@ -158,14 +152,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-ja/strings.xml b/libs/WindowManager/Shell/res/values-ja/strings.xml
index 3a12680395c6..d68581bda1ff 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>
@@ -115,14 +113,10 @@
<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>
@@ -158,14 +152,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..369c019726c7 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>
@@ -115,14 +113,10 @@
<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>
@@ -158,14 +152,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..1de32d4ae834 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>
@@ -115,14 +113,10 @@
<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>
@@ -158,14 +152,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..6c7faf1469eb 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>
@@ -115,14 +113,10 @@
<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>
@@ -158,14 +152,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..6a9e07f91d08 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>
@@ -115,14 +113,10 @@
<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>
@@ -158,14 +152,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..e94d8893010f 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>
@@ -115,14 +113,10 @@
<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>
@@ -158,14 +152,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..73e743686b77 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>
@@ -115,14 +113,10 @@
<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>
@@ -158,14 +152,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..fd47465de5da 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>
@@ -115,14 +113,10 @@
<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>
@@ -158,14 +152,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..902ef048b11e 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>
@@ -115,14 +113,10 @@
<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>
@@ -158,14 +152,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..b4095dfd6f1e 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>
@@ -115,14 +113,10 @@
<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>
@@ -158,14 +152,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..be97489d6159 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>
@@ -115,14 +113,10 @@
<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>
@@ -158,14 +152,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..cb84dec8a254 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>
@@ -115,14 +113,10 @@
<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>
@@ -158,14 +152,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..d8efd8b2a103 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>
@@ -115,14 +113,10 @@
<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>
@@ -158,14 +152,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..d09a78426669 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>
@@ -115,14 +113,10 @@
<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>
@@ -158,14 +152,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..e954b0f8937e 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>
@@ -115,14 +113,10 @@
<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>
@@ -158,14 +152,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..2342564930b4 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>
@@ -115,14 +113,10 @@
<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>
@@ -158,14 +152,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..fb61a105eaaa 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>
@@ -115,14 +113,10 @@
<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>
@@ -158,14 +152,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..fdfa227b8080 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>
@@ -115,14 +113,10 @@
<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>
@@ -158,14 +152,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..578877c65dd1 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>
@@ -115,14 +113,10 @@
<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>
@@ -158,14 +152,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..62ee4a049a1d 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>
@@ -115,14 +113,10 @@
<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>
@@ -158,14 +152,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..4dde6d292148 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>
@@ -115,14 +113,10 @@
<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>
@@ -158,14 +152,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..50861bac617b 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>
@@ -115,14 +113,10 @@
<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>
@@ -158,14 +152,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..a8f7403a94db 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>
@@ -115,14 +113,10 @@
<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>
@@ -158,14 +152,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..04d404287327 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>
@@ -115,14 +113,10 @@
<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>
@@ -158,14 +152,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..a8f7403a94db 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>
@@ -115,14 +113,10 @@
<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>
@@ -158,14 +152,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..d781d0893fa6 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>
@@ -115,14 +113,10 @@
<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>
@@ -158,14 +152,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..1837c31cbeef 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>
@@ -115,14 +113,10 @@
<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>
@@ -158,14 +152,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..d2fc08229793 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>
@@ -115,14 +113,10 @@
<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>
@@ -158,14 +152,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..a9c81a2e59ad 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>
@@ -115,14 +113,10 @@
<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>
@@ -158,14 +152,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..8dcca185e374 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>
@@ -115,14 +113,10 @@
<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>
@@ -158,14 +152,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..6f8bdef18dfe 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>
@@ -115,19 +113,15 @@
<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>
+ <string name="desktop_text" msgid="1582173066857454541">"Pamja e desktopit"</string>
<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 +134,7 @@
<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>
+ <string name="desktop_mode_app_header_chip_text" msgid="8300164817452574565">"<xliff:g id="APP_NAME">%1$s</xliff:g> (Pamja e desktopit)"</string>
<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 +152,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..287d34fc1de8 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>
@@ -115,14 +113,10 @@
<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>
@@ -158,14 +152,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..d8893dfab5f1 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>
@@ -115,14 +113,10 @@
<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>
@@ -158,14 +152,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..3ee43f3a69a8 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>
@@ -115,14 +113,10 @@
<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>
@@ -158,14 +152,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..a0e7c6acfb05 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>
@@ -115,14 +113,10 @@
<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>
@@ -158,14 +152,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..e0930132e6f3 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>
@@ -115,14 +113,10 @@
<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>
@@ -158,14 +152,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..594ee6c97689 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>
@@ -115,14 +113,10 @@
<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>
@@ -158,14 +152,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..f13de5b7846b 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>
@@ -115,14 +113,10 @@
<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>
@@ -158,14 +152,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..61fae05cc71c 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>
@@ -115,14 +113,10 @@
<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>
@@ -158,14 +152,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..ada82dfdc126 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>
@@ -115,14 +113,10 @@
<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>
@@ -158,14 +152,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..ded104cfd718 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>
@@ -115,14 +113,10 @@
<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>
@@ -158,14 +152,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..8a9dad029276 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>
@@ -115,14 +113,10 @@
<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>
@@ -158,14 +152,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..d6c8bc6173fb 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>
@@ -115,14 +113,10 @@
<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>
@@ -158,14 +152,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..1009afc62f55 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>
@@ -115,14 +113,10 @@
<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>
@@ -158,14 +152,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..edd67c17fcf4 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>
@@ -115,14 +113,10 @@
<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>
@@ -158,14 +152,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..32d87f3f8ce6 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>
@@ -115,14 +113,10 @@
<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>
@@ -158,14 +152,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..94f0d3476c5e 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>
@@ -115,14 +113,10 @@
<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>
@@ -158,14 +152,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..60044ad51d83 100644
--- a/libs/WindowManager/Shell/res/values/dimen.xml
+++ b/libs/WindowManager/Shell/res/values/dimen.xml
@@ -619,13 +619,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/res/values/dimen.xml b/libs/WindowManager/Shell/shared/res/values/dimen.xml
index 23c9caf2046c..f6a176fb3be8 100644
--- a/libs/WindowManager/Shell/shared/res/values/dimen.xml
+++ b/libs/WindowManager/Shell/shared/res/values/dimen.xml
@@ -39,6 +39,8 @@
<!-- Bubble drop target dimensions -->
<dimen name="drop_target_elevation">1dp</dimen>
+ <dimen name="drop_target_radius">28dp</dimen>
+ <dimen name="drop_target_stroke">1dp</dimen>
<dimen name="drop_target_full_screen_padding">20dp</dimen>
<dimen name="drop_target_desktop_window_padding_small">100dp</dimen>
<dimen name="drop_target_desktop_window_padding_large">130dp</dimen>
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 2dc183f3f707..2dbbeaebd3c0 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
@@ -18,29 +18,34 @@ package com.android.wm.shell.shared.bubbles
import android.content.Context
import android.graphics.Rect
-import android.view.View
+import android.graphics.RectF
+import android.view.ViewGroup.LayoutParams.MATCH_PARENT
import android.widget.FrameLayout
import androidx.core.animation.Animator
import androidx.core.animation.AnimatorListenerAdapter
import androidx.core.animation.ValueAnimator
+import com.android.wm.shell.shared.R
/**
* Manages animating drop targets in response to dragging bubble icons or bubble expanded views
* across different drag zones.
*/
class DropTargetManager(
- context: Context,
+ private val context: Context,
private val container: FrameLayout,
- private val isLayoutRtl: Boolean,
private val dragZoneChangedListener: DragZoneChangedListener,
) {
private var state: DragState? = null
- private val dropTargetView = View(context)
+ 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 ANIMATION_DURATION_MS = 250L
+ const val MORPH_ANIM_DURATION = 250L
+ const val DROP_TARGET_ALPHA_IN_DURATION = 150L
+ const val DROP_TARGET_ALPHA_OUT_DURATION = 100L
}
/** Must be called when a drag gesture is starting. */
@@ -55,15 +60,10 @@ class DropTargetManager(
private fun setupDropTarget() {
if (dropTargetView.parent != null) container.removeView(dropTargetView)
container.addView(dropTargetView, 0)
- // TODO b/393173014: set elevation and background
dropTargetView.alpha = 0f
- dropTargetView.scaleX = 1f
- dropTargetView.scaleY = 1f
- dropTargetView.translationX = 0f
- dropTargetView.translationY = 0f
- // the drop target is added with a width and height of 1 pixel. when it gets resized, we use
- // set its scale to the width and height of the bounds it should have to avoid layout passes
- dropTargetView.layoutParams = FrameLayout.LayoutParams(/* width= */ 1, /* height= */ 1)
+ dropTargetView.elevation = context.resources.getDimension(R.dimen.drop_target_elevation)
+ // Match parent and the target is drawn within the view
+ dropTargetView.layoutParams = FrameLayout.LayoutParams(MATCH_PARENT, MATCH_PARENT)
}
/** Called when the user drags to a new location. */
@@ -80,9 +80,11 @@ class DropTargetManager(
/** 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
}
@@ -92,10 +94,7 @@ class DropTargetManager(
when {
dropTargetBounds == null -> startFadeAnimation(from = dropTargetView.alpha, to = 0f)
dropTargetView.alpha == 0f -> {
- dropTargetView.translationX = dropTargetBounds.exactCenterX()
- dropTargetView.translationY = dropTargetBounds.exactCenterY()
- dropTargetView.scaleX = dropTargetBounds.width().toFloat()
- dropTargetView.scaleY = dropTargetBounds.height().toFloat()
+ dropTargetView.update(RectF(dropTargetBounds))
startFadeAnimation(from = 0f, to = 1f)
}
else -> startMorphAnimation(dropTargetBounds)
@@ -104,7 +103,9 @@ class DropTargetManager(
private fun startFadeAnimation(from: Float, to: Float, onEnd: (() -> Unit)? = null) {
animator?.cancel()
- val animator = ValueAnimator.ofFloat(from, to).setDuration(ANIMATION_DURATION_MS)
+ val duration =
+ if (from < to) DROP_TARGET_ALPHA_IN_DURATION else DROP_TARGET_ALPHA_OUT_DURATION
+ val animator = ValueAnimator.ofFloat(from, to).setDuration(duration)
animator.addUpdateListener { _ -> dropTargetView.alpha = animator.animatedValue as Float }
if (onEnd != null) {
animator.doOnEnd(onEnd)
@@ -113,23 +114,20 @@ class DropTargetManager(
animator.start()
}
- private fun startMorphAnimation(bounds: Rect) {
+ private fun startMorphAnimation(endBounds: Rect) {
animator?.cancel()
val startAlpha = dropTargetView.alpha
- val startTx = dropTargetView.translationX
- val startTy = dropTargetView.translationY
- val startScaleX = dropTargetView.scaleX
- val startScaleY = dropTargetView.scaleY
- val animator = ValueAnimator.ofFloat(0f, 1f).setDuration(ANIMATION_DURATION_MS)
+ val startRect = dropTargetView.getRect()
+ val animator = ValueAnimator.ofFloat(0f, 1f).setDuration(MORPH_ANIM_DURATION)
animator.addUpdateListener { _ ->
val fraction = animator.animatedValue as Float
dropTargetView.alpha = startAlpha + (1 - startAlpha) * fraction
- dropTargetView.translationX = startTx + (bounds.exactCenterX() - startTx) * fraction
- dropTargetView.translationY = startTy + (bounds.exactCenterY() - startTy) * fraction
- dropTargetView.scaleX =
- startScaleX + (bounds.width().toFloat() - startScaleX) * fraction
- dropTargetView.scaleY =
- startScaleY + (bounds.height().toFloat() - startScaleY) * fraction
+
+ morphRect.left = (startRect.left + (endBounds.left - startRect.left) * fraction)
+ morphRect.top = (startRect.top + (endBounds.top - startRect.top) * fraction)
+ morphRect.right = (startRect.right + (endBounds.right - startRect.right) * fraction)
+ morphRect.bottom = (startRect.bottom + (endBounds.bottom - startRect.bottom) * fraction)
+ dropTargetView.update(morphRect)
}
this.animator = animator
animator.start()
@@ -160,6 +158,9 @@ class DropTargetManager(
/** Called when the object was dragged to a different drag zone. */
fun onDragZoneChanged(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
new file mode 100644
index 000000000000..73277310ffe4
--- /dev/null
+++ b/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/bubbles/DropTargetView.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.wm.shell.shared.bubbles
+
+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
+
+/**
+ * Shows a drop target within this view.
+ */
+class DropTargetView(context: Context) : View(context) {
+
+ private val rectPaint = Paint(Paint.ANTI_ALIAS_FLAG).apply {
+ color = context.getColor(com.android.internal.R.color.materialColorPrimaryContainer)
+ style = Paint.Style.FILL
+ alpha = (0.35f * 255).toInt()
+ }
+
+ private val strokePaint = Paint(Paint.ANTI_ALIAS_FLAG).apply {
+ color = context.getColor(com.android.internal.R.color.materialColorPrimaryContainer)
+ style = Paint.Style.STROKE
+ strokeWidth = 1.dpToPx()
+ }
+
+ 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)
+ canvas.drawRoundRect(rect, cornerRadius, cornerRadius, strokePaint)
+ canvas.restore()
+ }
+
+ fun update(positionRect: RectF) {
+ rect.set(positionRect)
+ invalidate()
+ }
+
+ fun getRect(): RectF {
+ return RectF(rect)
+ }
+} \ No newline at end of file
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..53dede6bd227 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) {
@@ -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
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 305fcdd5fb7d..3e95a0b1100f 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
@@ -117,6 +117,7 @@ import com.android.wm.shell.shared.annotations.ShellBackgroundThread;
import com.android.wm.shell.shared.annotations.ShellMainThread;
import com.android.wm.shell.shared.bubbles.BubbleAnythingFlagHelper;
import com.android.wm.shell.shared.bubbles.BubbleBarLocation;
+import com.android.wm.shell.shared.bubbles.BubbleBarLocation.UpdateSource;
import com.android.wm.shell.shared.bubbles.BubbleBarUpdate;
import com.android.wm.shell.shared.bubbles.BubbleDropTargetBoundsProvider;
import com.android.wm.shell.shared.bubbles.DeviceConfig;
@@ -795,7 +796,7 @@ public class BubbleController implements ConfigurationChangeListener,
* Update bubble bar location and trigger and update to listeners
*/
public void setBubbleBarLocation(BubbleBarLocation bubbleBarLocation,
- @BubbleBarLocation.UpdateSource int source) {
+ @UpdateSource int source) {
if (isShowingAsBubbleBar()) {
updateExpandedViewForBubbleBarLocation(bubbleBarLocation, source);
BubbleBarUpdate bubbleBarUpdate = new BubbleBarUpdate();
@@ -805,7 +806,7 @@ public class BubbleController implements ConfigurationChangeListener,
}
private void updateExpandedViewForBubbleBarLocation(BubbleBarLocation bubbleBarLocation,
- @BubbleBarLocation.UpdateSource int source) {
+ @UpdateSource int source) {
if (isShowingAsBubbleBar()) {
BubbleBarLocation previousLocation = mBubblePositioner.getBubbleBarLocation();
mBubblePositioner.setBubbleBarLocation(bubbleBarLocation);
@@ -818,7 +819,7 @@ public class BubbleController implements ConfigurationChangeListener,
private void logBubbleBarLocationIfChanged(BubbleBarLocation location,
BubbleBarLocation previous,
- @BubbleBarLocation.UpdateSource int source) {
+ @UpdateSource int source) {
if (mLayerView == null) {
return;
}
@@ -830,25 +831,25 @@ public class BubbleController implements ConfigurationChangeListener,
return;
}
switch (source) {
- case BubbleBarLocation.UpdateSource.DRAG_BAR:
- case BubbleBarLocation.UpdateSource.A11Y_ACTION_BAR:
+ case UpdateSource.DRAG_BAR:
+ case UpdateSource.A11Y_ACTION_BAR:
mLogger.log(onLeft ? BubbleLogger.Event.BUBBLE_BAR_MOVED_LEFT_DRAG_BAR
: BubbleLogger.Event.BUBBLE_BAR_MOVED_RIGHT_DRAG_BAR);
break;
- case BubbleBarLocation.UpdateSource.DRAG_BUBBLE:
- case BubbleBarLocation.UpdateSource.A11Y_ACTION_BUBBLE:
+ case UpdateSource.DRAG_BUBBLE:
+ case UpdateSource.A11Y_ACTION_BUBBLE:
mLogger.log(onLeft ? BubbleLogger.Event.BUBBLE_BAR_MOVED_LEFT_DRAG_BUBBLE
: BubbleLogger.Event.BUBBLE_BAR_MOVED_RIGHT_DRAG_BUBBLE);
break;
- case BubbleBarLocation.UpdateSource.DRAG_EXP_VIEW:
- case BubbleBarLocation.UpdateSource.A11Y_ACTION_EXP_VIEW:
+ case UpdateSource.DRAG_EXP_VIEW:
+ case UpdateSource.A11Y_ACTION_EXP_VIEW:
// TODO(b/349845968): move logging from BubbleBarLayerView to here
break;
- case BubbleBarLocation.UpdateSource.APP_ICON_DRAG:
+ case UpdateSource.APP_ICON_DRAG:
mLogger.log(onLeft ? BubbleLogger.Event.BUBBLE_BAR_MOVED_LEFT_APP_ICON_DROP
: BubbleLogger.Event.BUBBLE_BAR_MOVED_RIGHT_APP_ICON_DROP);
break;
- case BubbleBarLocation.UpdateSource.DRAG_TASK:
+ case UpdateSource.DRAG_TASK:
mLogger.log(onLeft ? BubbleLogger.Event.BUBBLE_BAR_MOVED_LEFT_DRAG_TASK
: BubbleLogger.Event.BUBBLE_BAR_MOVED_RIGHT_DRAG_TASK);
break;
@@ -872,10 +873,7 @@ public class BubbleController implements ConfigurationChangeListener,
if (bubbleBarLocation == null) return;
if (isShowingAsBubbleBar() && BubbleAnythingFlagHelper.enableCreateAnyBubble()) {
mBubbleStateListener.onDragItemOverBubbleBarDragZone(bubbleBarLocation);
- ensureBubbleViewsAndWindowCreated();
- if (mLayerView != null) {
- mLayerView.showBubbleBarExtendedViewDropTarget(bubbleBarLocation);
- }
+ showBubbleBarExpandedViewDropTarget(bubbleBarLocation);
}
}
@@ -921,6 +919,13 @@ public class BubbleController implements ConfigurationChangeListener,
return result;
}
+ private void showBubbleBarExpandedViewDropTarget(BubbleBarLocation bubbleBarLocation) {
+ ensureBubbleViewsAndWindowCreated();
+ if (mLayerView != null) {
+ mLayerView.showBubbleBarExtendedViewDropTarget(bubbleBarLocation);
+ }
+ }
+
private void hideBubbleBarExpandedViewDropTarget() {
if (mLayerView != null) {
mLayerView.hideBubbleBarExpandedViewDropTarget();
@@ -1541,20 +1546,9 @@ public class BubbleController implements ConfigurationChangeListener,
public void expandStackAndSelectBubble(ShortcutInfo info,
@Nullable BubbleBarLocation bubbleBarLocation) {
if (!BubbleAnythingFlagHelper.enableCreateAnyBubble()) return;
- BubbleBarLocation updateLocation = isShowingAsBubbleBar() ? bubbleBarLocation : null;
- if (updateLocation != null) {
- updateExpandedViewForBubbleBarLocation(updateLocation,
- BubbleBarLocation.UpdateSource.APP_ICON_DRAG);
- }
Bubble b = mBubbleData.getOrCreateBubble(info); // Removes from overflow
ProtoLog.v(WM_SHELL_BUBBLES, "expandStackAndSelectBubble - shortcut=%s", info);
- if (b.isInflated()) {
- mBubbleData.setSelectedBubbleAndExpandStack(b, updateLocation);
- } else {
- b.enable(Notification.BubbleMetadata.FLAG_AUTO_EXPAND_BUBBLE);
- inflateAndAdd(b, /* suppressFlyout= */ true, /* showInShade= */ false,
- updateLocation);
- }
+ expandStackAndSelectAppBubble(b, bubbleBarLocation, UpdateSource.APP_ICON_DRAG);
}
/**
@@ -1562,16 +1556,12 @@ public class BubbleController implements ConfigurationChangeListener,
*
* @param intent the intent for the bubble.
*/
- public void expandStackAndSelectBubble(Intent intent, UserHandle user) {
+ public void expandStackAndSelectBubble(Intent intent, UserHandle user,
+ @Nullable BubbleBarLocation bubbleBarLocation) {
if (!BubbleAnythingFlagHelper.enableCreateAnyBubble()) return;
Bubble b = mBubbleData.getOrCreateBubble(intent, user); // Removes from overflow
ProtoLog.v(WM_SHELL_BUBBLES, "expandStackAndSelectBubble - intent=%s", intent);
- if (b.isInflated()) {
- mBubbleData.setSelectedBubbleAndExpandStack(b);
- } else {
- b.enable(Notification.BubbleMetadata.FLAG_AUTO_EXPAND_BUBBLE);
- inflateAndAdd(b, /* suppressFlyout= */ true, /* showInShade= */ false);
- }
+ expandStackAndSelectAppBubble(b, bubbleBarLocation, UpdateSource.APP_ICON_DRAG);
}
/**
@@ -1583,14 +1573,19 @@ public class BubbleController implements ConfigurationChangeListener,
public void expandStackAndSelectBubble(PendingIntent pendingIntent, UserHandle user,
@Nullable BubbleBarLocation bubbleBarLocation) {
if (!BubbleAnythingFlagHelper.enableCreateAnyBubble()) return;
+ Bubble b = mBubbleData.getOrCreateBubble(pendingIntent, user); // Removes from overflow
+ ProtoLog.v(WM_SHELL_BUBBLES, "expandStackAndSelectBubble - pendingIntent=%s",
+ pendingIntent);
+ expandStackAndSelectAppBubble(b, bubbleBarLocation, UpdateSource.APP_ICON_DRAG);
+ }
+
+ private void expandStackAndSelectAppBubble(Bubble b,
+ @Nullable BubbleBarLocation bubbleBarLocation, @UpdateSource int source) {
+ if (!BubbleAnythingFlagHelper.enableCreateAnyBubble()) return;
BubbleBarLocation updateLocation = isShowingAsBubbleBar() ? bubbleBarLocation : null;
if (updateLocation != null) {
- updateExpandedViewForBubbleBarLocation(updateLocation,
- BubbleBarLocation.UpdateSource.APP_ICON_DRAG);
+ updateExpandedViewForBubbleBarLocation(updateLocation, source);
}
- Bubble b = mBubbleData.getOrCreateBubble(pendingIntent, user);
- ProtoLog.v(WM_SHELL_BUBBLES, "expandStackAndSelectBubble - pendingIntent=%s",
- pendingIntent);
if (b.isInflated()) {
mBubbleData.setSelectedBubbleAndExpandStack(b, updateLocation);
} else {
@@ -1609,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 =
@@ -1623,7 +1618,7 @@ public class BubbleController implements ConfigurationChangeListener,
}
} else {
if (location != null) {
- setBubbleBarLocation(location, BubbleBarLocation.UpdateSource.DRAG_TASK);
+ setBubbleBarLocation(location, UpdateSource.DRAG_TASK);
}
b.enable(Notification.BubbleMetadata.FLAG_AUTO_EXPAND_BUBBLE);
// Lazy init stack view when a bubble is created
@@ -2648,6 +2643,15 @@ public class BubbleController implements ConfigurationChangeListener,
mBubbleData.setSelectedBubbleAndExpandStack(bubbleToSelect);
}
+ private void moveBubbleToFullscreen(String key) {
+ Bubble b = mBubbleData.getBubbleInStackWithKey(key);
+ if (b == null) {
+ Log.w(TAG, "can't find bubble with key " + key + " to move to fullscreen");
+ return;
+ }
+ b.getTaskView().moveToFullscreen();
+ }
+
private boolean isDeviceLocked() {
return !mIsStatusBarShade;
}
@@ -2840,14 +2844,16 @@ public class BubbleController implements ConfigurationChangeListener,
}
@Override
- public void showShortcutBubble(ShortcutInfo info) {
+ public void showShortcutBubble(ShortcutInfo info, @Nullable BubbleBarLocation location) {
mMainExecutor.execute(() -> mController
- .expandStackAndSelectBubble(info, /* bubbleBarLocation = */ null));
+ .expandStackAndSelectBubble(info, location));
}
@Override
- public void showAppBubble(Intent intent, UserHandle user) {
- mMainExecutor.execute(() -> mController.expandStackAndSelectBubble(intent, user));
+ public void showAppBubble(Intent intent, UserHandle user,
+ @Nullable BubbleBarLocation location) {
+ mMainExecutor.execute(
+ () -> mController.expandStackAndSelectBubble(intent, user, location));
}
@Override
@@ -2900,7 +2906,7 @@ public class BubbleController implements ConfigurationChangeListener,
@Override
public void setBubbleBarLocation(BubbleBarLocation location,
- @BubbleBarLocation.UpdateSource int source) {
+ @UpdateSource int source) {
mMainExecutor.execute(() ->
mController.setBubbleBarLocation(location, source));
}
@@ -2921,6 +2927,22 @@ public class BubbleController implements ConfigurationChangeListener,
}
});
}
+
+ @Override
+ public void showDropTarget(boolean show, BubbleBarLocation location) {
+ mMainExecutor.execute(() -> {
+ if (show) {
+ showBubbleBarExpandedViewDropTarget(location);
+ } else {
+ hideBubbleBarExpandedViewDropTarget();
+ }
+ });
+ }
+
+ @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/BubblePositioner.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubblePositioner.java
index 33f1b94bac73..70340d7032b4 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
@@ -136,6 +136,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;
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..d5f2dbdbf5f5 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;
@@ -155,28 +156,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 +192,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 +367,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 +434,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 +444,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);
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 0a4d79a6c297..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
@@ -51,9 +51,13 @@ interface IBubbles {
oneway void stopBubbleDrag(in BubbleBarLocation location, in int topOnScreen) = 11;
- oneway void showShortcutBubble(in ShortcutInfo info) = 12;
+ oneway void showShortcutBubble(in ShortcutInfo info, in @nullable BubbleBarLocation location) = 12;
- oneway void showAppBubble(in Intent intent, in UserHandle user) = 13;
+ oneway void showAppBubble(in Intent intent, in UserHandle user, in @nullable BubbleBarLocation location) = 13;
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..bdb21f246359 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,13 @@ 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.DropTargetManager;
import kotlin.Unit;
@@ -76,6 +81,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 +132,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 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 +345,8 @@ public class BubbleBarLayerView extends FrameLayout
mAnimationHelper,
mPositioner,
mBubbleExpandedViewPinController,
+ mDropTargetManager,
+ mDragZoneFactory,
dragListener);
addView(mExpandedView, new LayoutParams(width, height, Gravity.LEFT));
@@ -348,13 +423,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 +447,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/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/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/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 b507ca2019a9..453ca167557a 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,17 +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;
@@ -37,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 Flags.enableConnectedDisplaysPip();
+ return DesktopExperienceFlags.ENABLE_CONNECTED_DISPLAYS_PIP.isTrue() && Flags.enablePip2();
}
/** Returns whether the display with the PiP task is in freeform windowing mode. */
@@ -88,45 +86,10 @@ public class PipDesktopState {
return false;
}
final int displayId = mPipDisplayLayoutState.getDisplayId();
- return getDesktopRepository().isAnyDeskActive(displayId)
- || getDesktopWallpaperActivityTokenProvider().isWallpaperActivityVisible(displayId)
+ return mDesktopUserRepositoriesOptional.get().getCurrent().isAnyDeskActive(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);
- }
-
/**
* The windowing mode to restore to when resizing out of PIP direction.
* Defaults to undefined and can be overridden to restore to an alternate windowing mode.
@@ -149,11 +112,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/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..318cdeec5bc1 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
@@ -1052,23 +1052,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..92c3020a14e6 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,6 +96,7 @@ 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.DesktopTaskChangeListener;
import com.android.wm.shell.desktopmode.DesktopTasksController;
@@ -167,6 +171,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 +453,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 +462,8 @@ public abstract class WMShellModule {
desktopImmersiveController,
windowDecorViewModel,
taskChangeListener,
- focusTransitionObserver);
+ focusTransitionObserver,
+ desksTransitionObserver);
}
@WMSingleton
@@ -722,9 +729,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 +759,7 @@ public abstract class WMShellModule {
ToggleResizeDesktopTaskTransitionHandler toggleResizeDesktopTaskTransitionHandler,
DragToDesktopTransitionHandler dragToDesktopTransitionHandler,
@DynamicOverride DesktopUserRepositories desktopUserRepositories,
+ DesktopRepositoryInitializer desktopRepositoryInitializer,
Optional<DesktopImmersiveController> desktopImmersiveController,
DesktopModeLoggerTransitionObserver desktopModeLoggerTransitionObserver,
LaunchAdjacentController launchAdjacentController,
@@ -769,10 +779,12 @@ public abstract class WMShellModule {
Optional<BubbleController> bubbleController,
OverviewToDesktopTransitionObserver overviewToDesktopTransitionObserver,
DesksOrganizer desksOrganizer,
- DesksTransitionObserver desksTransitionObserver,
+ Optional<DesksTransitionObserver> desksTransitionObserver,
UserProfileContexts userProfileContexts,
DesktopModeCompatPolicy desktopModeCompatPolicy,
- DragToDisplayTransitionHandler dragToDisplayTransitionHandler) {
+ DragToDisplayTransitionHandler dragToDisplayTransitionHandler,
+ DesktopModeMoveToDisplayTransitionHandler moveToDisplayTransitionHandler,
+ HomeIntentProvider homeIntentProvider) {
return new DesktopTasksController(
context,
shellInit,
@@ -794,6 +806,7 @@ public abstract class WMShellModule {
dragToDesktopTransitionHandler,
desktopImmersiveController.get(),
desktopUserRepositories,
+ desktopRepositoryInitializer,
recentsTransitionHandler,
multiInstanceHelper,
mainExecutor,
@@ -809,10 +822,12 @@ public abstract class WMShellModule {
bubbleController,
overviewToDesktopTransitionObserver,
desksOrganizer,
- desksTransitionObserver,
+ desksTransitionObserver.get(),
userProfileContexts,
desktopModeCompatPolicy,
- dragToDisplayTransitionHandler);
+ dragToDisplayTransitionHandler,
+ moveToDisplayTransitionHandler,
+ homeIntentProvider);
}
@WMSingleton
@@ -869,12 +884,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 +897,8 @@ public abstract class WMShellModule {
transitions,
desktopUserRepositories,
shellTaskOrganizer,
- maxTaskLimit,
+ desksOrganizer,
+ maxTaskLimit <= 0 ? null : maxTaskLimit,
interactionJankMonitor,
context,
handler));
@@ -938,12 +954,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 +1024,7 @@ public abstract class WMShellModule {
Optional<DesktopTasksLimiter> desktopTasksLimiter,
AppHandleEducationController appHandleEducationController,
AppToWebEducationController appToWebEducationController,
+ AppHandleAndHeaderVisibilityHelper appHandleAndHeaderVisibilityHelper,
WindowDecorCaptionHandleRepository windowDecorCaptionHandleRepository,
Optional<DesktopActivityOrientationChangeHandler> activityOrientationChangeHandler,
FocusTransitionObserver focusTransitionObserver,
@@ -1019,10 +1048,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 +1079,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,
@@ -1188,7 +1227,6 @@ public abstract class WMShellModule {
Optional<DesktopMixedTransitionHandler> desktopMixedTransitionHandler,
Optional<BackAnimationController> backAnimationController,
DesktopWallpaperActivityTokenProvider desktopWallpaperActivityTokenProvider,
- @NonNull DesksTransitionObserver desksTransitionObserver,
ShellInit shellInit) {
return desktopUserRepositories.flatMap(
repository ->
@@ -1201,17 +1239,21 @@ public abstract class WMShellModule {
desktopMixedTransitionHandler.get(),
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
@@ -1273,10 +1315,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 +1329,9 @@ public abstract class WMShellModule {
new DesktopDisplayEventHandler(
context,
shellInit,
+ mainScope,
displayController,
+ desktopRepositoryInitializer,
desktopUserRepositories.get(),
desktopTasksController.get(),
desktopDisplayModeController.get()));
@@ -1520,7 +1566,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 +1587,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/DesktopMinimizationTransitionHandler.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopMinimizationTransitionHandler.kt
index 7074e8bc9cce..6c6d830e915e 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopMinimizationTransitionHandler.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopMinimizationTransitionHandler.kt
@@ -21,6 +21,7 @@ import android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM
import android.os.Handler
import android.os.IBinder
import android.view.SurfaceControl.Transaction
+import android.view.WindowManager.TRANSIT_TO_BACK
import android.window.TransitionInfo
import android.window.TransitionRequestInfo
import android.window.WindowContainerTransaction
@@ -61,7 +62,9 @@ class DesktopMinimizationTransitionHandler(
finishTransaction: Transaction,
finishCallback: Transitions.TransitionFinishCallback,
): Boolean {
- if (!TransitionUtil.isClosingType(info.type)) return false
+ val shouldAnimate =
+ TransitionUtil.isClosingType(info.type) || info.type == Transitions.TRANSIT_MINIMIZE
+ if (!shouldAnimate) return false
val animations = mutableListOf<Animator>()
val onAnimFinish: (Animator) -> Unit = { animator ->
@@ -75,10 +78,14 @@ class DesktopMinimizationTransitionHandler(
}
}
+ val checkChangeMode = { change: TransitionInfo.Change ->
+ change.mode == info.type ||
+ (info.type == Transitions.TRANSIT_MINIMIZE && change.mode == TRANSIT_TO_BACK)
+ }
animations +=
info.changes
.filter {
- it.mode == info.type && it.taskInfo?.windowingMode == WINDOWING_MODE_FREEFORM
+ checkChangeMode(it) && it.taskInfo?.windowingMode == WINDOWING_MODE_FREEFORM
}
.mapNotNull { createMinimizeAnimation(it, finishTransaction, onAnimFinish) }
if (animations.isEmpty()) return false
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/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/DesktopModeShellCommandHandler.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeShellCommandHandler.kt
index aa507728739e..e3a71a1ad9a5 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeShellCommandHandler.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeShellCommandHandler.kt
@@ -18,6 +18,7 @@ package com.android.wm.shell.desktopmode
import android.app.ActivityTaskManager.INVALID_TASK_ID
import android.window.DesktopExperienceFlags
+import com.android.wm.shell.desktopmode.DesktopModeEventLogger.Companion.UnminimizeReason
import com.android.wm.shell.shared.desktopmode.DesktopModeTransitionSource.UNKNOWN
import com.android.wm.shell.sysui.ShellCommandHandler
import com.android.wm.shell.transition.FocusTransitionObserver
@@ -188,8 +189,12 @@ class DesktopModeShellCommandHandler(
pw.println("Error: task id should be an integer")
return false
}
- pw.println("Not implemented.")
- return false
+ controller.moveTaskToFront(
+ /* taskId= */ taskId,
+ /* remoteTransition= */ null,
+ /* unminimizeReason= */ UnminimizeReason.UNKNOWN,
+ )
+ return true
}
private fun runMoveTaskOutOfDesk(args: Array<String>, pw: PrintWriter): Boolean {
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..fa98d0339a65 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
@@ -22,6 +22,13 @@ 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 +37,9 @@ import android.content.res.Configuration.ORIENTATION_PORTRAIT
import android.graphics.Rect
import android.os.SystemProperties
import android.util.Size
+import android.window.DesktopModeFlags
import com.android.wm.shell.R
+import com.android.wm.shell.ShellTaskOrganizer
import com.android.wm.shell.common.DisplayController
import com.android.wm.shell.common.DisplayLayout
import kotlin.math.ceil
@@ -264,6 +273,58 @@ fun getAppHeaderHeight(context: Context): Int =
@DimenRes fun getAppHeaderHeightId(): Int = R.dimen.desktop_mode_freeform_decor_caption_height
/**
+ * 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 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
* scale of the screen bounds.
*/
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..b3b4d59090e8 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
@@ -32,6 +32,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 +104,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 +115,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 +131,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 +147,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 +171,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 +197,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 {
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..8636bc1f56c2 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
@@ -70,9 +69,6 @@ class DesktopRepository(
* 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,
@@ -86,8 +82,6 @@ class DesktopRepository(
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(
@@ -101,7 +95,6 @@ class DesktopRepository(
fullImmersiveTaskId = fullImmersiveTaskId,
topTransparentFullscreenTaskId = topTransparentFullscreenTaskId,
pipTaskId = pipTaskId,
- pipShouldKeepDesktopActive = pipShouldKeepDesktopActive,
)
// TODO: b/362720497 - remove when multi-desktops is enabled where instances aren't
@@ -115,7 +108,6 @@ class DesktopRepository(
fullImmersiveTaskId = null
topTransparentFullscreenTaskId = null
pipTaskId = null
- pipShouldKeepDesktopActive = true
}
}
@@ -243,6 +235,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)
@@ -493,7 +489,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) }
@@ -625,7 +621,6 @@ class DesktopRepository(
?: error("Expected active desk in display: $displayId")
if (enterPip) {
activeDesk.pipTaskId = taskId
- activeDesk.pipShouldKeepDesktopActive = true
} else {
activeDesk.pipTaskId =
if (activeDesk.pipTaskId == taskId) null
@@ -638,19 +633,9 @@ class DesktopRepository(
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.
@@ -659,25 +644,6 @@ class DesktopRepository(
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.
@@ -750,12 +716,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 +740,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 +821,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 +922,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 +935,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 +1039,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 +1075,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 6034299453fb..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
@@ -35,7 +35,7 @@ class DesktopTaskChangeListener(private val desktopUserRepositories: DesktopUser
desktopRepository.removeTask(taskInfo.displayId, taskInfo.taskId)
return
}
- if (isFreeformTask(taskInfo)) {
+ if (isFreeformTask(taskInfo) && !desktopRepository.isActiveTask(taskInfo.taskId)) {
desktopRepository.addTask(taskInfo.displayId, taskInfo.taskId, taskInfo.isVisible)
}
}
@@ -44,23 +44,21 @@ class DesktopTaskChangeListener(private val desktopUserRepositories: DesktopUser
logD("onTaskChanging for taskId=%d, displayId=%d", taskInfo.taskId, taskInfo.displayId)
val desktopRepository: DesktopRepository =
desktopUserRepositories.getProfile(taskInfo.userId)
- if (!desktopRepository.isActiveTask(taskInfo.taskId)) return
-
// TODO: b/394281403 - with multiple desks, it's possible to have a non-freeform task
// inside a desk, so this should be decoupled from windowing mode.
// Also, changes in/out of desks are handled by the [DesksTransitionObserver], which has
// more specific information about the desk involved in the transition, which might be
// more accurate than assuming it's always the default/active desk in the display, as this
// method does.
- // Case 1: Freeform task is changed in Desktop Mode.
- if (isFreeformTask(taskInfo)) {
- if (taskInfo.isVisible) {
- desktopRepository.addTask(taskInfo.displayId, taskInfo.taskId, taskInfo.isVisible)
- }
- desktopRepository.updateTask(taskInfo.displayId, taskInfo.taskId, taskInfo.isVisible)
- } else {
- // Case 2: Freeform task is changed outside Desktop Mode.
+ // 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) && 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)
}
}
@@ -82,16 +80,24 @@ class DesktopTaskChangeListener(private val desktopUserRepositories: DesktopUser
logD("onTaskMovingToFront for taskId=%d, displayId=%d", taskInfo.taskId, taskInfo.displayId)
val desktopRepository: DesktopRepository =
desktopUserRepositories.getProfile(taskInfo.userId)
- if (!desktopRepository.isActiveTask(taskInfo.taskId)) return
- if (!isFreeformTask(taskInfo)) {
+ // 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) && desktopRepository.isActiveTask(taskInfo.taskId)) {
desktopRepository.removeTask(taskInfo.displayId, taskInfo.taskId)
}
- desktopRepository.addTask(taskInfo.displayId, taskInfo.taskId, taskInfo.isVisible)
+ if (isFreeformTask(taskInfo)) {
+ // If the task is already active in the repository, then it only moves the task to the
+ // front.
+ desktopRepository.addTask(taskInfo.displayId, taskInfo.taskId, taskInfo.isVisible)
+ }
}
override fun onTaskMovingToBack(taskInfo: RunningTaskInfo) {
+ val desktopRepository: DesktopRepository =
+ 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) {
@@ -101,7 +107,7 @@ class DesktopTaskChangeListener(private val desktopUserRepositories: DesktopUser
if (!desktopRepository.isActiveTask(taskInfo.taskId)) return
// TODO: b/370038902 - Handle Activity#finishAndRemoveTask.
if (
- !DesktopModeFlags.ENABLE_DESKTOP_WINDOWING_BACK_NAVIGATION.isTrue() ||
+ !DesktopModeFlags.ENABLE_DESKTOP_WINDOWING_BACK_NAVIGATION.isTrue ||
desktopRepository.isClosingTask(taskInfo.taskId)
) {
// A task that's vanishing should be removed:
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 b47dfe2064cd..d0356d55035d 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
@@ -31,6 +32,8 @@ import android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED
import android.app.WindowConfiguration.WindowingMode
import android.content.Context
import android.content.Intent
+import android.content.Intent.FLAG_ACTIVITY_MULTIPLE_TASK
+import android.content.Intent.FLAG_ACTIVITY_NEW_TASK
import android.graphics.Point
import android.graphics.PointF
import android.graphics.Rect
@@ -47,6 +50,7 @@ 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
@@ -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,
@@ -208,6 +217,8 @@ class DesktopTasksController(
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 +244,10 @@ class DesktopTasksController(
removeVisualIndicator()
}
+ override fun onTransitionInterrupted() {
+ removeVisualIndicator()
+ }
+
private fun removeVisualIndicator() {
visualIndicator?.fadeOutIndicator { releaseVisualIndicator() }
}
@@ -265,6 +280,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 +371,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
@@ -537,8 +548,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 +578,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 +612,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,67 +627,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)
- }
- if (DesktopExperienceFlags.ENABLE_MULTIPLE_DESKTOPS_BACKEND.isTrue) {
- prepareMoveTaskToDesk(wct, task, deskId)
- } else {
- addMoveToDesktopChanges(wct, task)
- }
- 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
@@ -716,6 +666,7 @@ class DesktopTasksController(
dragToDesktopTransitionHandler.startDragToDesktopTransition(
taskInfo,
dragToDesktopValueAnimator,
+ visualIndicator,
)
}
@@ -742,13 +693,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,
@@ -761,20 +706,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 {
@@ -822,7 +756,6 @@ class DesktopTasksController(
displayId = displayId,
forceExitDesktop = false,
)
- taskRepository.setPipShouldKeepDesktopActive(displayId, keepActive = true)
val desktopExitRunnable =
performDesktopExitCleanUp(
wct = wct,
@@ -855,7 +788,9 @@ class DesktopTasksController(
fun minimizeTask(taskInfo: RunningTaskInfo, minimizeReason: MinimizeReason) {
val wct = WindowContainerTransaction()
- val isMinimizingToPip = taskInfo.pictureInPictureParams?.isAutoEnterEnabled() ?: false
+ val isMinimizingToPip =
+ DesktopModeFlags.ENABLE_DESKTOP_WINDOWING_PIP.isTrue &&
+ (taskInfo.pictureInPictureParams?.isAutoEnterEnabled() ?: false)
// If task is going to PiP, start a PiP transition instead of a minimize transition
if (isMinimizingToPip) {
val requestInfo =
@@ -892,7 +827,6 @@ class DesktopTasksController(
val wct = WindowContainerTransaction()
snapEventHandler.removeTaskIfTiled(displayId, taskId)
- taskRepository.setPipShouldKeepDesktopActive(displayId, keepActive = true)
val willExitDesktop = willExitDesktop(taskId, displayId, forceExitDesktop = false)
val desktopExitRunnable =
performDesktopExitCleanUp(
@@ -1007,11 +941,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
@@ -1057,10 +989,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,
)
@@ -1078,19 +1013,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,
)
@@ -1102,14 +1044,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,
@@ -1121,21 +1071,28 @@ class DesktopTasksController(
excludeTaskId = launchingTaskId,
reason = DesktopImmersiveController.ExitReason.TASK_LAUNCH,
)
- var deskIdToActivate: Int? = null
- if (
- DesktopExperienceFlags.ENABLE_DISPLAY_WINDOWING_MODE_SWITCHING.isTrue &&
- !isDesktopModeShowing(displayId)
- ) {
- deskIdToActivate =
- checkNotNull(
- launchingTaskId?.let { taskRepository.getDeskIdForTask(it) }
- ?: getDefaultDeskId(displayId)
- )
+ var activationRunOnTransitStart: RunOnTransitStart? = null
+ val shouldActivateDesk =
+ when {
+ DesktopExperienceFlags.ENABLE_MULTIPLE_DESKTOPS_BACKEND.isTrue ->
+ !taskRepository.isDeskActive(deskId)
+ DesktopExperienceFlags.ENABLE_DISPLAY_WINDOWING_MODE_SWITCHING.isTrue -> {
+ !isDesktopModeShowing(displayId)
+ }
+ else -> false
+ }
+ if (shouldActivateDesk) {
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
+ desktopModeEnterExitTransitionListener?.onEnterDesktopModeTransitionStarted(
+ FREEFORM_ANIMATION_DURATION
+ )
}
val t =
if (remoteTransition == null) {
@@ -1169,24 +1126,7 @@ class DesktopTasksController(
if (launchingTaskId != null && taskRepository.isMinimizedTask(launchingTaskId)) {
addPendingUnminimizeTransition(t, displayId, launchingTaskId, unminimizeReason)
}
- if (
- DesktopExperienceFlags.ENABLE_MULTIPLE_DESKTOPS_BACKEND.isTrue &&
- deskIdToActivate != null
- ) {
- if (DesktopExperienceFlags.ENABLE_DISPLAY_WINDOWING_MODE_SWITCHING.isTrue) {
- desksTransitionObserver.addPendingTransition(
- DeskTransition.ActivateDesk(
- token = t,
- displayId = displayId,
- deskId = deskIdToActivate,
- )
- )
- }
-
- desktopModeEnterExitTransitionListener?.onEnterDesktopModeTransitionStarted(
- FREEFORM_ANIMATION_DURATION
- )
- }
+ activationRunOnTransitStart?.invoke(t)
exitImmersiveResult.asExit()?.runOnTransitionStart?.invoke(t)
return t
}
@@ -1250,16 +1190,29 @@ class DesktopTasksController(
pendingIntentBackgroundActivityStartMode =
ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_ALLOW_ALWAYS
launchBounds = bounds
+ if (DesktopModeFlags.ENABLE_SHELL_INITIAL_BOUNDS_REGRESSION_BUG_FIX.isTrue) {
+ // Sets launch bounds size as flexible so core can recalculate.
+ flexibleLaunchSize = true
+ }
}
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,
+ )
}
/**
* Move [task] to display with [displayId].
*
* No-op if task is already on that display per [RunningTaskInfo.displayId].
+ *
+ * TODO: b/399411604 - split this up into smaller functions.
*/
private fun moveToDisplay(task: RunningTaskInfo, displayId: Int) {
logV("moveToDisplay: taskId=%d displayId=%d", task.taskId, displayId)
@@ -1268,6 +1221,11 @@ class DesktopTasksController(
return
}
+ if (splitScreenController.isTaskInSplitScreen(task.taskId)) {
+ moveSplitPairToDisplay(task, displayId)
+ return
+ }
+
val wct = WindowContainerTransaction()
val displayAreaInfo = rootTaskDisplayAreaOrganizer.getDisplayAreaInfo(displayId)
if (displayAreaInfo == null) {
@@ -1275,38 +1233,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")
@@ -1315,27 +1241,22 @@ class DesktopTasksController(
// TODO: b/393977830 and b/397437641 - do not assume that freeform==desktop.
if (!task.isFreeform) {
- addMoveToDesktopChanges(wct, task, displayId)
- } else if (Flags.enableMoveToNextDisplayShortcut()) {
- applyFreeformDisplayChange(wct, task, displayId)
+ addMoveToDeskTaskChanges(wct = wct, task = task, deskId = destinationDeskId)
+ } else {
+ if (DesktopExperienceFlags.ENABLE_MULTIPLE_DESKTOPS_BACKEND.isTrue) {
+ desksOrganizer.moveTaskToDesk(wct, destinationDeskId, task)
+ }
+ if (Flags.enableMoveToNextDisplayShortcut()) {
+ applyFreeformDisplayChange(wct, task, displayId)
+ }
}
- if (DesktopExperienceFlags.ENABLE_MULTIPLE_DESKTOPS_BACKEND.isTrue) {
- desksOrganizer.moveTaskToDesk(wct, destinationDeskId, task)
- } else {
+ if (!DesktopExperienceFlags.ENABLE_MULTIPLE_DESKTOPS_BACKEND.isTrue) {
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,
- )
- )
- }
+
+ // TODO: b/391485148 - pass in the moving-to-desk |task| here to apply task-limit policy.
+ val activationRunnable = addDeskActivationChanges(destinationDeskId, wct)
if (Flags.enableDisplayFocusInShellTransitions()) {
// Bring the destination display to top with includingParents=true, so that the
@@ -1366,12 +1287,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).
@@ -1731,13 +1700,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,
@@ -1769,15 +1735,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())
}
}
@@ -1796,34 +1754,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) {
@@ -1909,11 +1840,7 @@ class DesktopTasksController(
displayId: Int,
forceExitDesktop: Boolean,
): Boolean {
- if (
- forceExitDesktop &&
- (Flags.enableDesktopWindowingPip() ||
- DesktopExperienceFlags.ENABLE_MULTIPLE_DESKTOPS_BACKEND.isTrue)
- ) {
+ if (forceExitDesktop && DesktopExperienceFlags.ENABLE_MULTIPLE_DESKTOPS_BACKEND.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.
return true
@@ -1922,11 +1849,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
@@ -1943,7 +1865,6 @@ class DesktopTasksController(
forceToFullscreen: Boolean,
shouldEndUpAtHome: Boolean = true,
): RunOnTransitStart? {
- taskRepository.setPipShouldKeepDesktopActive(displayId, keepActive = !forceToFullscreen)
if (!willExitDesktop(taskId, displayId, forceToFullscreen)) {
return null
}
@@ -2062,12 +1983,13 @@ class DesktopTasksController(
triggerTask?.let { task ->
when {
// Check if freeform task launch during recents should be handled
- shouldHandleMidRecentsFreeformLaunch -> handleMidRecentsFreeformTaskLaunch(task)
+ shouldHandleMidRecentsFreeformLaunch ->
+ handleMidRecentsFreeformTaskLaunch(task, transition)
// Check if the closing task needs to be handled
TransitionUtil.isClosingType(request.type) ->
handleTaskClosing(task, transition, request.type)
// Check if the top task shouldn't be allowed to enter desktop mode
- isIncompatibleTask(task) -> handleIncompatibleTaskLaunch(task)
+ isIncompatibleTask(task) -> handleIncompatibleTaskLaunch(task, transition)
// Check if fullscreen task should be updated
task.isFullscreen -> handleFullscreenTaskLaunch(task, transition)
// Check if freeform task should be updated
@@ -2217,6 +2139,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)
@@ -2224,11 +2147,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) {
@@ -2255,10 +2180,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,
)
}
@@ -2306,20 +2235,23 @@ class DesktopTasksController(
* This is a special case where we want to launch the task in fullscreen instead of freeform.
*/
private fun handleMidRecentsFreeformTaskLaunch(
- task: RunningTaskInfo
+ task: RunningTaskInfo,
+ transition: IBinder,
): WindowContainerTransaction? {
logV("DesktopTasksController: handleMidRecentsFreeformTaskLaunch")
val wct = WindowContainerTransaction()
- addMoveToFullscreenChanges(
- wct = wct,
- taskInfo = task,
- willExitDesktop =
- willExitDesktop(
- triggerTaskId = task.taskId,
- displayId = task.displayId,
- forceExitDesktop = true,
- ),
- )
+ val runOnTransitStart =
+ addMoveToFullscreenChanges(
+ wct = wct,
+ taskInfo = task,
+ willExitDesktop =
+ willExitDesktop(
+ triggerTaskId = task.taskId,
+ displayId = task.displayId,
+ forceExitDesktop = true,
+ ),
+ )
+ runOnTransitStart?.invoke(transition)
wct.reorder(task.token, true)
return wct
}
@@ -2335,6 +2267,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)
@@ -2343,27 +2276,38 @@ class DesktopTasksController(
// launched. We should make this task go to fullscreen instead of freeform. Note
// that this means any re-launch of a freeform window outside of desktop will be in
// fullscreen as long as default-desktop flag is disabled.
- addMoveToFullscreenChanges(
- wct = wct,
- taskInfo = task,
- willExitDesktop =
- willExitDesktop(
- triggerTaskId = task.taskId,
- displayId = task.displayId,
- forceExitDesktop = true,
- ),
- )
+ val runOnTransitStart =
+ addMoveToFullscreenChanges(
+ wct = wct,
+ taskInfo = task,
+ willExitDesktop =
+ willExitDesktop(
+ triggerTaskId = task.taskId,
+ displayId = task.displayId,
+ forceExitDesktop = true,
+ ),
+ )
+ 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)
@@ -2395,7 +2339,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)
@@ -2416,26 +2360,44 @@ class DesktopTasksController(
if (shouldFullscreenTaskLaunchSwitchToDesktop(task)) {
logD("Switch fullscreen task to freeform on transition: taskId=%d", task.taskId)
return WindowContainerTransaction().also { wct ->
- addMoveToDesktopChanges(wct, task)
- // 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 deskId = getDefaultDeskId(task.displayId)
+ addMoveToDeskTaskChanges(wct = wct, task = task, deskId = deskId)
+ 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,
@@ -2447,7 +2409,8 @@ class DesktopTasksController(
// If a freeform task receives a request for a fullscreen launch, apply the same
// changes we do for similar transitions. The task not having WINDOWING_MODE_UNDEFINED
// set when needed can interfere with future split / multi-instance transitions.
- return WindowContainerTransaction().also { wct ->
+ val wct = WindowContainerTransaction()
+ val runOnTransitStart =
addMoveToFullscreenChanges(
wct = wct,
taskInfo = task,
@@ -2458,7 +2421,8 @@ class DesktopTasksController(
forceExitDesktop = true,
),
)
- }
+ runOnTransitStart?.invoke(transition)
+ return wct
}
return null
}
@@ -2473,7 +2437,10 @@ class DesktopTasksController(
* If a task is not compatible with desktop mode freeform, it should always be launched in
* fullscreen.
*/
- private fun handleIncompatibleTaskLaunch(task: RunningTaskInfo): WindowContainerTransaction? {
+ private fun handleIncompatibleTaskLaunch(
+ task: RunningTaskInfo,
+ transition: IBinder,
+ ): WindowContainerTransaction? {
logV("handleIncompatibleTaskLaunch")
if (!isDesktopModeShowing(task.displayId) && !forceEnterDesktop(task.displayId)) return null
// Only update task repository for transparent task.
@@ -2485,7 +2452,8 @@ class DesktopTasksController(
}
// Already fullscreen, no-op.
if (task.isFullscreen) return null
- return WindowContainerTransaction().also { wct ->
+ val wct = WindowContainerTransaction()
+ val runOnTransitStart =
addMoveToFullscreenChanges(
wct = wct,
taskInfo = task,
@@ -2496,7 +2464,8 @@ class DesktopTasksController(
forceExitDesktop = true,
),
)
- }
+ runOnTransitStart?.invoke(transition)
+ return wct
}
/**
@@ -2543,55 +2512,66 @@ class DesktopTasksController(
}
/**
- * Apply all changes required when task is first added to desktop. Uses the task's current
- * display by default to apply initial bounds and placement relative to the display. Use a
- * different [displayId] if the task should be moved to a different display.
+ * Applies the [wct] changes need when a task is first moving to a desk and the desk needs to be
+ * activated.
*/
- @VisibleForTesting
- @Deprecated("Deprecated with multiple desks", ReplaceWith("prepareMoveTaskToDesk()"))
- fun addMoveToDesktopChanges(
+ private fun addDeskActivationWithMovingTaskChanges(
+ deskId: Int,
wct: WindowContainerTransaction,
- taskInfo: RunningTaskInfo,
- displayId: Int = taskInfo.displayId,
- ) {
- val displayLayout = displayController.getDisplayLayout(displayId) ?: return
- val tdaInfo = rootTaskDisplayAreaOrganizer.getDisplayAreaInfo(displayId)!!
- val tdaWindowingMode = tdaInfo.configuration.windowConfiguration.windowingMode
- // TODO: b/397437641 - reconsider the windowing mode choice when multiple desks is enabled.
- val targetWindowingMode =
- if (tdaWindowingMode == WINDOWING_MODE_FREEFORM) {
- // Display windowing is freeform, set to undefined and inherit it
- WINDOWING_MODE_UNDEFINED
- } else {
- WINDOWING_MODE_FREEFORM
- }
- val initialBounds = getInitialBounds(displayLayout, taskInfo, displayId)
-
- if (canChangeTaskPosition(taskInfo)) {
- wct.setBounds(taskInfo.token, initialBounds)
- }
- wct.setWindowingMode(taskInfo.token, targetWindowingMode)
- wct.reorder(taskInfo.token, /* onTop= */ true)
- if (useDesktopOverrideDensity()) {
- wct.setDensityDpi(taskInfo.token, DESKTOP_DENSITY_OVERRIDE)
- }
+ task: RunningTaskInfo,
+ ): RunOnTransitStart? {
+ val runOnTransitStart = addDeskActivationChanges(deskId, wct, task)
+ addMoveToDeskTaskChanges(wct = wct, task = task, deskId = deskId)
+ return runOnTransitStart
}
- private fun prepareMoveTaskToDesk(
+ /**
+ * 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
+ * transferring a task between desks.
+ *
+ * TODO: b/362720497 - this should be improved to be reusable by desk-to-desk CUJs where
+ * [DesksOrganizer.moveTaskToDesk] needs to be called and even cross-display CUJs where
+ * [applyFreeformDisplayChange] needs to be called. Potentially by comparing source vs
+ * destination desk ids and display ids, or adding extra arguments to the function.
+ */
+ fun addMoveToDeskTaskChanges(
wct: WindowContainerTransaction,
- taskInfo: RunningTaskInfo,
+ task: RunningTaskInfo,
deskId: Int,
) {
- if (!DesktopExperienceFlags.ENABLE_MULTIPLE_DESKTOPS_BACKEND.isTrue) return
- val displayId = taskRepository.getDisplayForDesk(deskId)
- val displayLayout = displayController.getDisplayLayout(displayId) ?: return
- val initialBounds = getInitialBounds(displayLayout, taskInfo, displayId)
- if (canChangeTaskPosition(taskInfo)) {
- wct.setBounds(taskInfo.token, initialBounds)
+ val targetDisplayId = taskRepository.getDisplayForDesk(deskId)
+ val displayLayout = displayController.getDisplayLayout(targetDisplayId) ?: return
+ 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)
+ } else {
+ val tdaInfo = rootTaskDisplayAreaOrganizer.getDisplayAreaInfo(targetDisplayId)!!
+ val tdaWindowingMode = tdaInfo.configuration.windowConfiguration.windowingMode
+ val targetWindowingMode =
+ if (tdaWindowingMode == WINDOWING_MODE_FREEFORM) {
+ // Display windowing is freeform, set to undefined and inherit it
+ WINDOWING_MODE_UNDEFINED
+ } else {
+ WINDOWING_MODE_FREEFORM
+ }
+ wct.setWindowingMode(task.token, targetWindowingMode)
+ wct.reorder(task.token, /* onTop= */ true)
}
- desksOrganizer.moveTaskToDesk(wct, deskId = deskId, task = taskInfo)
if (useDesktopOverrideDensity()) {
- wct.setDensityDpi(taskInfo.token, DESKTOP_DENSITY_OVERRIDE)
+ wct.setDensityDpi(task.token, DESKTOP_DENSITY_OVERRIDE)
}
}
@@ -2692,7 +2672,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,
@@ -2750,7 +2729,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,
@@ -2759,7 +2738,7 @@ class DesktopTasksController(
require(newTaskId == null || !launchingNewIntent)
return desktopTasksLimiter
.get()
- .addAndGetMinimizeTaskChanges(displayId, wct, newTaskId, launchingNewIntent)
+ .addAndGetMinimizeTaskChanges(deskId, wct, newTaskId, launchingNewIntent)
}
private fun addPendingMinimizeTransition(
@@ -2821,28 +2800,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 =
@@ -2852,15 +2901,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
@@ -3423,7 +3464,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)
}
@@ -3468,6 +3516,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")
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..7dabeb7c9d15 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
@@ -25,7 +25,6 @@ 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,12 +32,10 @@ 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.desktopmode.DesktopModeStatus
@@ -60,7 +57,6 @@ class DesktopTasksTransitionObserver(
private val desktopMixedTransitionHandler: DesktopMixedTransitionHandler,
private val backAnimationController: BackAnimationController,
private val desktopWallpaperActivityTokenProvider: DesktopWallpaperActivityTokenProvider,
- private val desksTransitionObserver: DesksTransitionObserver,
shellInit: ShellInit,
) : Transitions.TransitionObserver {
@@ -90,7 +86,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()
@@ -108,7 +103,7 @@ class DesktopTasksTransitionObserver(
info.changes.forEach { change ->
change.taskInfo?.let { taskInfo ->
if (
- Flags.enableDesktopWindowingPip() &&
+ DesktopModeFlags.ENABLE_DESKTOP_WINDOWING_PIP.isTrue &&
desktopRepository.isTaskMinimizedPipInDisplay(
taskInfo.displayId,
taskInfo.taskId,
@@ -332,10 +327,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 +337,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 -> {}
}
}
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..24b2e4879546 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
@@ -24,7 +24,10 @@ import android.os.SystemClock
import android.os.SystemProperties
import android.os.UserHandle
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
@@ -118,6 +121,7 @@ sealed class DragToDesktopTransitionHandler(
fun startDragToDesktopTransition(
taskInfo: RunningTaskInfo,
dragToDesktopAnimator: MoveToDesktopAnimator,
+ visualIndicator: DesktopModeVisualIndicator?,
) {
if (inProgress) {
logV("Drag to desktop transition already in progress.")
@@ -163,12 +167,14 @@ sealed class DragToDesktopTransitionHandler(
dragAnimator = dragToDesktopAnimator,
startTransitionToken = startTransitionToken,
otherSplitTask = otherTask,
+ visualIndicator = visualIndicator,
)
} else {
TransitionState.FromFullscreen(
draggedTaskId = taskInfo.taskId,
dragAnimator = dragToDesktopAnimator,
startTransitionToken = startTransitionToken,
+ visualIndicator = visualIndicator,
)
}
}
@@ -181,18 +187,29 @@ 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")
+ // We should only have interrupted the start transition after receiving a cancel/end
+ // request, let that existing request play out and just return here.
+ return null
+ }
+ state.endTransitionToken =
+ transitions.startTransition(TRANSIT_DESKTOP_MODE_END_DRAG_TO_DESKTOP, wct, this)
+ return state.endTransitionToken
}
/**
@@ -216,6 +233,11 @@ sealed class DragToDesktopTransitionHandler(
clearState()
return
}
+ if (state.startInterrupted) {
+ // We should only have interrupted the start transition after receiving a cancel/end
+ // request, let that existing request play out and just return here.
+ return
+ }
state.cancelState = cancelState
if (state.draggedTaskChange != null && cancelState == CancelState.STANDARD_CANCEL) {
@@ -223,7 +245,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 +273,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 +299,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 +311,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 +343,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 +375,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 +496,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 +531,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 +555,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 +673,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,6 +701,41 @@ 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 &&
+ isCancelOrEndTransitionRequested(state) &&
+ !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
+ }
+ logV("interruptStartTransition")
+ state.startTransitionFinishCb?.onTransitionFinished(/* wct= */ null)
+ state.dragAnimator.cancelAnimator()
+ 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)
}
protected open fun setupEndDragToDesktop(
@@ -755,7 +902,7 @@ sealed class DragToDesktopTransitionHandler(
} ?: false
}
- private fun startCancelAnimation() {
+ private fun startCancelAnimation(): Animator {
val state = requireTransitionState()
val dragToDesktopAnimator = state.dragAnimator
@@ -772,7 +919,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 +937,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 +1030,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 +1055,11 @@ 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?
data class FromFullscreen(
override val draggedTaskId: Int,
@@ -915,6 +1074,11 @@ 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,
var otherRootChanges: MutableList<Change> = mutableListOf(),
) : TransitionState()
@@ -931,6 +1095,11 @@ 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,
var splitRootChange: Change? = null,
var otherSplitTask: Int,
) : TransitionState()
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/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/docs/dagger.md b/libs/WindowManager/Shell/src/com/android/wm/shell/docs/dagger.md
index 9b09904527bf..84525a741480 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/docs/dagger.md
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/docs/dagger.md
@@ -1,4 +1,5 @@
# Usage of Dagger in the Shell library
+
[Back to home](README.md)
---
@@ -16,36 +17,69 @@ As such, the Shell also tries to provide some reasonable out-of-the-box modules
## Modules
All the Dagger related code in the Shell can be found in the `com.android.wm.shell.dagger` package,
-this is intentional as it keeps the "magic" in a single location. The explicit nature of how
+this is intentional as it keeps the "magic" in a single location. The explicit nature of how
components in the shell are provided is as a result a bit more verbose, but it makes it easy for
developers to jump into a few select files and understand how different components are provided
(especially as products override components).
The module dependency tree looks a bit like:
+
- [WMShellConcurrencyModule](/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellConcurrencyModule.java)
(provides threading-related components)
- - [WMShellBaseModule](/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java)
- (provides components that are likely common to all products, ie. DisplayController,
- Transactions, etc.)
- - [WMShellModule](/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java)
- (phone/tablet specific components only)
- - [TvPipModule](/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/TvPipModule.java)
- (PIP specific components for TV)
- - [TvWMShellModule](/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/TvWMShellModule.java)
- (TV specific components only)
- - etc.
+ - [WMShellBaseModule](/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java)
+ (provides components that are common to many products, ie. DisplayController, Transactions,
+ etc.)
+ - [WMShellModule](/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java)
+ (phone/tablet specific components only)
+ - [TvPipModule](/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/TvPipModule.java)
+ (PIP specific components for TV)
+ - [TvWMShellModule](/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/TvWMShellModule.java)
+ (TV specific components only)
+ - etc.
Ideally features could be abstracted out into their own modules and included as needed by each
product.
+## Changing WMShellBaseModule
+
+Because all products will include WMShellBaseModule, we don't want it to provide instances for
+features that aren't used across multiple products (ie. Handheld, TV, Auto, Wear). This module
+should generally only provide:
+
+- Concrete implementations that are needed for startup
+ (see `provideIndependentShellComponentsToCreate()`)
+- Things used directly/indirectly by interfaces
+ exposed to SysUI
+ in [WMComponent.java](/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMComponent.java).
+
+For the latter, not every feature will be enabled on the SysUI form factor including the base
+module, so the recommendation is to have an `@BindsOptionalOf` for the interface, and have the
+actual implementation provided in the form-factor specific module (ie. `WMShellModule`).
+
## Overriding base components
In some rare cases, there are base components that can change behavior depending on which
-product it runs on. If there are hooks that can be added to the component, that is the
+product it runs on. If there are hooks that can be added to the component, that is the
preferable approach.
-The alternative is to use the [@DynamicOverride](/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/DynamicOverride.java)
+The alternative is to use
+the [@DynamicOverride](/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/DynamicOverride.java)
annotation to allow the product module to provide an implementation that the base module can
-reference. This is most useful if the existence of the entire component is controlled by the
-product and the override implementation is optional (there is a default implementation). More
-details can be found in the class's javadoc. \ No newline at end of file
+reference. This is most useful if the existence of the entire component is controlled by the
+product and the override implementation is optional (there is a default implementation). More
+details can be found in the class's javadoc.
+
+## Starting up Shell components aren't dependencies for other components
+
+With Dagger, objects are created in dependency order and individual components can register with
+`ShellInit` (see [Component initialization](changes.md#component-initialization)) to initialize in
+dependency order as well. However, if there is code that needs to run on startup but has nothing
+dependent on it (imagine a background error detector for example), then
+`provideIndependentShellComponentsToCreate()` can serve as the artificial dependent object (itself
+a dependency for `ShellInterface`) to trigger creation of such a component.
+
+This can be declared within each module, so if a product includes `WMShellModule`, all the
+components in `provideIndependentShellComponentsToCreate()` for both it and `WMShellBaseModule` will
+be created.
+
+Note that long term we are looking to move to a `CoreStartable` like infrastructure. \ No newline at end of file
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..f89ba0a168d1 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.
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/freeform/OWNERS b/libs/WindowManager/Shell/src/com/android/wm/shell/freeform/OWNERS
index 44d46eea9c55..7a63ec5eeda3 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/freeform/OWNERS
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/freeform/OWNERS
@@ -2,7 +2,6 @@
atsjenk@google.com
jorgegil@google.com
madym@google.com
-nmusgrave@google.com
pbdr@google.com
vaniadesmonda@google.com
pragyabajoria@google.com
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/keyguard/KeyguardTransitionHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/keyguard/KeyguardTransitionHandler.java
index d666126b91ba..c0a0f469add4 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/keyguard/KeyguardTransitionHandler.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/keyguard/KeyguardTransitionHandler.java
@@ -22,6 +22,7 @@ import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM;
import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
import static android.service.dreams.Flags.dismissDreamOnKeyguardDismiss;
import static android.view.WindowManager.KEYGUARD_VISIBILITY_TRANSIT_FLAGS;
+import static android.view.WindowManager.TRANSIT_FLAG_AOD_APPEARING;
import static android.view.WindowManager.TRANSIT_FLAG_KEYGUARD_APPEARING;
import static android.view.WindowManager.TRANSIT_FLAG_KEYGUARD_GOING_AWAY;
import static android.view.WindowManager.TRANSIT_FLAG_KEYGUARD_LOCKED;
@@ -200,7 +201,8 @@ public class KeyguardTransitionHandler
transition, info, startTransaction, finishTransaction, finishCallback);
}
- if ((info.getFlags() & TRANSIT_FLAG_KEYGUARD_APPEARING) != 0) {
+ if ((info.getFlags() & TRANSIT_FLAG_KEYGUARD_APPEARING) != 0
+ || (info.getFlags() & TRANSIT_FLAG_AOD_APPEARING) != 0) {
return startAnimation(mAppearTransition, "appearing",
transition, info, startTransaction, finishTransaction, finishCallback);
}
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/pip/tv/OWNERS b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/OWNERS
index 5aa3c4e2abef..245669b644db 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/OWNERS
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/OWNERS
@@ -1,3 +1,2 @@
# WM shell sub-module TV pip owner
-galinap@google.com
bronger@google.com
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/animation/PipAlphaAnimator.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/animation/PipAlphaAnimator.java
index a033b824aa28..7918a216836f 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/animation/PipAlphaAnimator.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/animation/PipAlphaAnimator.java
@@ -131,9 +131,10 @@ public class PipAlphaAnimator extends ValueAnimator {
}
private void onAlphaAnimationUpdate(float alpha, SurfaceControl.Transaction tx) {
+ // only set shadow radius on fade in
tx.setAlpha(mLeash, alpha)
.setCornerRadius(mLeash, mCornerRadius)
- .setShadowRadius(mLeash, mShadowRadius);
+ .setShadowRadius(mLeash, mDirection == FADE_IN ? mShadowRadius : 0f);
tx.apply();
}
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 1ce24f76ada5..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
@@ -32,6 +32,7 @@ import android.content.res.Configuration;
import android.graphics.Rect;
import android.os.Bundle;
import android.view.SurfaceControl;
+import android.window.DesktopExperienceFlags;
import android.window.DisplayAreaInfo;
import android.window.WindowContainerTransaction;
@@ -41,7 +42,6 @@ import androidx.annotation.Nullable;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.protolog.ProtoLog;
import com.android.internal.util.Preconditions;
-import com.android.window.flags.Flags;
import com.android.wm.shell.R;
import com.android.wm.shell.ShellTaskOrganizer;
import com.android.wm.shell.common.DisplayChangeController;
@@ -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();
@@ -303,7 +306,8 @@ public class PipController implements ConfigurationChangeListener,
public void onDisplayRemoved(int displayId) {
// If PiP was active on an external display that is removed, clean up states and set
// {@link PipDisplayLayoutState} to DEFAULT_DISPLAY.
- if (Flags.enableConnectedDisplaysPip() && mPipTransitionState.isInPip()
+ if (DesktopExperienceFlags.ENABLE_CONNECTED_DISPLAYS_PIP.isTrue()
+ && mPipTransitionState.isInPip()
&& displayId == mPipDisplayLayoutState.getDisplayId()
&& displayId != DEFAULT_DISPLAY) {
mPipTransitionState.setState(PipTransitionState.EXITING_PIP);
@@ -345,7 +349,6 @@ public class PipController implements ConfigurationChangeListener,
return;
}
- mPipTouchHandler.updateMinMaxSize(mPipBoundsState.getAspectRatio());
mPipMenuController.hideMenu();
if (mPipTransitionState.isInFixedRotation()) {
@@ -366,6 +369,8 @@ public class PipController implements ConfigurationChangeListener,
mPipBoundsState.setBounds(toBounds);
}
t.setBounds(mPipTransitionState.getPipTaskToken(), mPipBoundsState.getBounds());
+ // Update the size spec in PipBoundsState afterwards.
+ mPipBoundsState.updateMinMaxSize(mPipBoundsState.getAspectRatio());
}
private void setDisplayLayout(DisplayLayout layout) {
@@ -384,7 +389,7 @@ public class PipController implements ConfigurationChangeListener,
// If PiP is enabled on Connected Displays, update PipDisplayLayoutState to have the correct
// display info that PiP is entering in.
- if (Flags.enableConnectedDisplaysPip()) {
+ if (DesktopExperienceFlags.ENABLE_CONNECTED_DISPLAYS_PIP.isTrue()) {
final DisplayLayout displayLayout = mDisplayController.getDisplayLayout(displayId);
if (displayLayout != null) {
mPipDisplayLayoutState.setDisplayId(displayId);
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/PipResizeGestureHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipResizeGestureHandler.java
index af1e98a3a8d2..d53365abd143 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipResizeGestureHandler.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipResizeGestureHandler.java
@@ -81,8 +81,6 @@ public class PipResizeGestureHandler implements
private final PointF mDownSecondPoint = new PointF();
private final PointF mLastPoint = new PointF();
private final PointF mLastSecondPoint = new PointF();
- private final Point mMaxSize = new Point();
- private final Point mMinSize = new Point();
private final Rect mLastResizeBounds = new Rect();
private final Rect mUserResizeBounds = new Rect();
private final Rect mDownBounds = new Rect();
@@ -95,7 +93,6 @@ public class PipResizeGestureHandler implements
private boolean mIsEnabled;
private boolean mEnablePinchResize;
private boolean mEnableDragCornerResize;
- private boolean mIsSysUiStateValid;
private boolean mThresholdCrossed;
private boolean mOngoingPinchToResize = false;
private boolean mWaitingForBoundsChangeTransition = false;
@@ -152,7 +149,6 @@ public class PipResizeGestureHandler implements
}
void init() {
- mContext.getDisplay().getRealSize(mMaxSize);
reloadResources();
final Resources res = mContext.getResources();
@@ -163,15 +159,6 @@ public class PipResizeGestureHandler implements
reloadResources();
}
- /**
- * Called when SysUI state changed.
- *
- * @param isSysUiStateValid Is SysUI valid or not.
- */
- public void onSystemUiStateChanged(boolean isSysUiStateValid) {
- mIsSysUiStateValid = isSysUiStateValid;
- }
-
private void reloadResources() {
mPipDragToResizeHandler.reloadResources();
mTouchSlop = ViewConfiguration.get(mContext).getScaledTouchSlop();
@@ -287,13 +274,15 @@ public class PipResizeGestureHandler implements
}
}
+ final Point minSize = mPipBoundsState.getMinSize();
+ final Point maxSize = mPipBoundsState.getMaxSize();
if (mOngoingPinchToResize) {
mPipPinchToResizeHandler.onPinchResize(mv, mDownPoint, mDownSecondPoint,
mDownBounds, mLastPoint, mLastSecondPoint, mLastResizeBounds, mTouchSlop,
- mMinSize, mMaxSize);
+ minSize, maxSize);
} else if (mEnableDragCornerResize) {
mPipDragToResizeHandler.onDragCornerResize(mv, mLastResizeBounds, mDownPoint,
- mDownBounds, mMinSize, mMaxSize, mTouchSlop);
+ mDownBounds, minSize, maxSize, mTouchSlop);
}
}
}
@@ -327,7 +316,7 @@ public class PipResizeGestureHandler implements
if (mEnablePinchResize && ev.getPointerCount() == 2) {
mPipPinchToResizeHandler.onPinchResize(ev, mDownPoint, mDownSecondPoint,
mDownBounds, mLastPoint, mLastSecondPoint, mLastResizeBounds,
- mTouchSlop, mMinSize, mMaxSize);
+ mTouchSlop, mPipBoundsState.getMinSize(), mPipBoundsState.getMaxSize());
mOngoingPinchToResize = mAllowGesture;
return mAllowGesture;
}
@@ -407,16 +396,19 @@ public class PipResizeGestureHandler implements
return;
}
+ final Point minSize = mPipBoundsState.getMinSize();
+ final Point maxSize = mPipBoundsState.getMaxSize();
+
// If user resize is pretty close to max size, just auto resize to max.
- if (mLastResizeBounds.width() >= PINCH_RESIZE_AUTO_MAX_RATIO * mMaxSize.x
- || mLastResizeBounds.height() >= PINCH_RESIZE_AUTO_MAX_RATIO * mMaxSize.y) {
- resizeRectAboutCenter(mLastResizeBounds, mMaxSize.x, mMaxSize.y);
+ if (mLastResizeBounds.width() >= PINCH_RESIZE_AUTO_MAX_RATIO * maxSize.x
+ || mLastResizeBounds.height() >= PINCH_RESIZE_AUTO_MAX_RATIO * maxSize.y) {
+ resizeRectAboutCenter(mLastResizeBounds, maxSize.x, maxSize.y);
}
// If user resize is smaller than min size, auto resize to min
- if (mLastResizeBounds.width() < mMinSize.x
- || mLastResizeBounds.height() < mMinSize.y) {
- resizeRectAboutCenter(mLastResizeBounds, mMinSize.x, mMinSize.y);
+ if (mLastResizeBounds.width() < minSize.x
+ || mLastResizeBounds.height() < minSize.y) {
+ resizeRectAboutCenter(mLastResizeBounds, minSize.x, minSize.y);
}
// get the current movement bounds
@@ -472,15 +464,6 @@ public class PipResizeGestureHandler implements
mInputMonitor.pilferPointers();
}
-
- void updateMaxSize(int maxX, int maxY) {
- mMaxSize.set(maxX, maxY);
- }
-
- void updateMinSize(int minX, int minY) {
- mMinSize.set(minX, minY);
- }
-
void setOhmOffset(int offset) {
mOhmOffset = offset;
}
@@ -568,8 +551,6 @@ public class PipResizeGestureHandler implements
pw.println(innerPrefix + "mEnableDragCornerResize=" + mEnableDragCornerResize);
pw.println(innerPrefix + "mThresholdCrossed=" + mThresholdCrossed);
pw.println(innerPrefix + "mOhmOffset=" + mOhmOffset);
- pw.println(innerPrefix + "mMinSize=" + mMinSize);
- pw.println(innerPrefix + "mMaxSize=" + mMaxSize);
}
class PipResizeInputEventReceiver extends BatchedInputEventReceiver {
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..f81f330e50c4 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;
@@ -238,12 +241,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 +296,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 +325,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 72346b335a8e..6fdfecaf15d5 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
@@ -230,10 +230,7 @@ public class PipTouchHandler implements PipTransitionState.PipTransitionStateCha
pipBoundsState, mTouchState, mPipScheduler, mPipTransitionState, pipUiEventLogger,
menuController, this::getMovementBounds, mPipDisplayLayoutState, pipDesktopState,
mainExecutor, mPipPerfHintController);
- mPipBoundsState.addOnAspectRatioChangedCallback(aspectRatio -> {
- updateMinMaxSize(aspectRatio);
- onAspectRatioChanged();
- });
+ mPipBoundsState.addOnAspectRatioChangedCallback(aspectRatio -> onAspectRatioChanged());
mMoveOnShelVisibilityChanged = () -> {
if (mIsImeShowing && mImeHeight > mShelfHeight) {
@@ -418,15 +415,6 @@ public class PipTouchHandler implements PipTransitionState.PipTransitionStateCha
mMainExecutor.executeDelayed(mMoveOnShelVisibilityChanged, PIP_KEEP_CLEAR_AREAS_DELAY);
}
- /**
- * Called when SysUI state changed.
- *
- * @param isSysUiStateValid Is SysUI valid or not.
- */
- public void onSystemUiStateChanged(boolean isSysUiStateValid) {
- mPipResizeGestureHandler.onSystemUiStateChanged(isSysUiStateValid);
- }
-
void adjustBoundsForRotation(Rect outBounds, Rect curBounds, Rect insetBounds) {
final Rect toMovementBounds = new Rect();
mPipBoundsAlgorithm.getMovementBounds(outBounds, insetBounds, toMovementBounds, 0);
@@ -480,8 +468,6 @@ public class PipTouchHandler implements PipTransitionState.PipTransitionStateCha
mPipBoundsState.getExpandedBounds(), insetBounds, expandedMovementBounds,
bottomOffset);
- updatePipSizeConstraints(normalBounds, aspectRatio);
-
// The extra offset does not really affect the movement bounds, but are applied based on the
// current state (ime showing, or shelf offset) when we need to actually shift
int extraOffset = Math.max(
@@ -507,35 +493,6 @@ public class PipTouchHandler implements PipTransitionState.PipTransitionStateCha
}
/**
- * Update the values for min/max allowed size of picture in picture window based on the aspect
- * ratio.
- * @param aspectRatio aspect ratio to use for the calculation of min/max size
- */
- public void updateMinMaxSize(float aspectRatio) {
- updatePipSizeConstraints(mPipBoundsState.getNormalBounds(),
- aspectRatio);
- }
-
- private void updatePipSizeConstraints(Rect normalBounds,
- float aspectRatio) {
- if (mPipResizeGestureHandler.isUsingPinchToZoom()) {
- updatePinchResizeSizeConstraints(aspectRatio);
- } else {
- mPipResizeGestureHandler.updateMinSize(normalBounds.width(), normalBounds.height());
- mPipResizeGestureHandler.updateMaxSize(mPipBoundsState.getExpandedBounds().width(),
- mPipBoundsState.getExpandedBounds().height());
- }
- }
-
- private void updatePinchResizeSizeConstraints(float aspectRatio) {
- mPipBoundsState.updateMinMaxSize(aspectRatio);
- mPipResizeGestureHandler.updateMinSize(mPipBoundsState.getMinSize().x,
- mPipBoundsState.getMinSize().y);
- mPipResizeGestureHandler.updateMaxSize(mPipBoundsState.getMaxSize().x,
- mPipBoundsState.getMaxSize().y);
- }
-
- /**
* TODO Add appropriate description
*/
public void onRegistrationChanged(boolean isRegistered) {
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 93023471fdfb..c370c0cb0930 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
@@ -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;
@@ -787,7 +787,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 +833,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 +848,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 +1050,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 +1140,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 +1645,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
@@ -2080,8 +2109,8 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
Math.max(topLeftBounds.top, 0);
bottomRightBounds.right =
Math.min(bottomRightBounds.right, mSplitLayout.getDisplayWidth());
- bottomRightBounds.top =
- Math.min(bottomRightBounds.top, mSplitLayout.getDisplayHeight());
+ bottomRightBounds.bottom =
+ Math.min(bottomRightBounds.bottom, mSplitLayout.getDisplayHeight());
// TODO (b/349828130): Can change to getState() fully after brief soak time.
if (mSplitState.get() != currentSnapPosition) {
@@ -2890,8 +2919,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 +2928,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 +3007,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 +3202,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 +3588,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/splitscreen/tv/OWNERS b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/tv/OWNERS
index 28be0efc38f6..9dc0ebbb8e56 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/tv/OWNERS
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/tv/OWNERS
@@ -1,3 +1,2 @@
# WM shell sub-module TV splitscreen owner
-galinap@google.com
bronger@google.com
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultTransitionHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultTransitionHandler.java
index 3652a1661f28..bf5800330979 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultTransitionHandler.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultTransitionHandler.java
@@ -95,7 +95,6 @@ import android.os.UserHandle;
import android.util.ArrayMap;
import android.view.SurfaceControl;
import android.view.WindowManager;
-import android.view.animation.AlphaAnimation;
import android.view.animation.Animation;
import android.window.TransitionInfo;
import android.window.TransitionMetrics;
@@ -835,9 +834,8 @@ public class DefaultTransitionHandler implements Transitions.TransitionHandler {
a = mTransitionAnimation.loadVoiceActivityExitAnimation(enter, userId);
}
} else if (changeMode == TRANSIT_CHANGE) {
- // In the absence of a specific adapter, we just want to keep everything stationary.
- a = new AlphaAnimation(1.f, 1.f);
- a.setDuration(TransitionAnimation.DEFAULT_APP_TRANSITION_DURATION);
+ // Apply end state directly by default.
+ return null;
} else if (type == TRANSIT_RELAUNCH) {
a = mTransitionAnimation.createRelaunchAnimation(endBounds, mInsets, endBounds);
} else if (overrideType == ANIM_CUSTOM
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..a1d2774ee428 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;
@@ -79,7 +77,6 @@ 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.WindowContainerToken;
@@ -121,7 +118,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 +142,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;
@@ -207,6 +204,7 @@ 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 boolean mTransitionDragActive;
@@ -294,6 +292,7 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel,
Optional<DesktopTasksLimiter> desktopTasksLimiter,
AppHandleEducationController appHandleEducationController,
AppToWebEducationController appToWebEducationController,
+ AppHandleAndHeaderVisibilityHelper appHandleAndHeaderVisibilityHelper,
WindowDecorCaptionHandleRepository windowDecorCaptionHandleRepository,
Optional<DesktopActivityOrientationChangeHandler> activityOrientationChangeHandler,
FocusTransitionObserver focusTransitionObserver,
@@ -338,6 +337,7 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel,
desktopTasksLimiter,
appHandleEducationController,
appToWebEducationController,
+ appHandleAndHeaderVisibilityHelper,
windowDecorCaptionHandleRepository,
activityOrientationChangeHandler,
new TaskPositionerFactory(),
@@ -386,6 +386,7 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel,
Optional<DesktopTasksLimiter> desktopTasksLimiter,
AppHandleEducationController appHandleEducationController,
AppToWebEducationController appToWebEducationController,
+ AppHandleAndHeaderVisibilityHelper appHandleAndHeaderVisibilityHelper,
WindowDecorCaptionHandleRepository windowDecorCaptionHandleRepository,
Optional<DesktopActivityOrientationChangeHandler> activityOrientationChangeHandler,
TaskPositionerFactory taskPositionerFactory,
@@ -431,6 +432,7 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel,
mDesktopTasksLimiter = desktopTasksLimiter;
mAppHandleEducationController = appHandleEducationController;
mAppToWebEducationController = appToWebEducationController;
+ mAppHandleAndHeaderVisibilityHelper = appHandleAndHeaderVisibilityHelper;
mWindowDecorCaptionHandleRepository = windowDecorCaptionHandleRepository;
mActivityOrientationChangeHandler = activityOrientationChangeHandler;
mAssistContentRequester = assistContentRequester;
@@ -528,6 +530,7 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel,
@Override
public void setSplitScreenController(SplitScreenController splitScreenController) {
mSplitScreenController = splitScreenController;
+ mAppHandleAndHeaderVisibilityHelper.setSplitScreenController(splitScreenController);
}
@Override
@@ -1215,7 +1218,7 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel,
if (id == R.id.caption_handle) {
handleCaptionThroughStatusBar(e, decoration);
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 +1234,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 +1260,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 +1287,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 +1320,7 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel,
// onClick call that results.
return false;
} else {
- updateDragStatus(e.getActionMasked());
+ updateDragStatus(decoration, e);
return true;
}
}
@@ -1318,16 +1328,16 @@ 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;
+ setIsDragging(decor, false /* isDragging */);
break;
}
case MotionEvent.ACTION_MOVE: {
- mIsDragging = true;
+ setIsDragging(decor, true /* isDragging */);
break;
}
}
@@ -1717,32 +1727,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(
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..673c3f66cb6c 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
@@ -29,6 +29,7 @@ import static android.window.DesktopModeFlags.ENABLE_CAPTION_COMPAT_INSET_FORCE_
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;
@@ -207,7 +208,7 @@ 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;
@@ -513,7 +514,7 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin
updateRelayoutParams(mRelayoutParams, mContext, taskInfo, mSplitScreenController,
applyStartTransactionOnDraw, shouldSetTaskVisibilityPositionAndCrop,
mIsStatusBarVisible, mIsKeyguardVisibleAndOccluded, inFullImmersive,
- mDisplayController.getInsetsState(taskInfo.displayId), hasGlobalFocus,
+ mIsDragging, mDisplayController.getInsetsState(taskInfo.displayId), hasGlobalFocus,
displayExclusionRegion, mIsRecentsTransitionRunning,
mDesktopModeCompatPolicy.shouldExcludeCaptionFromAppBounds(taskInfo));
@@ -911,6 +912,7 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin
boolean isStatusBarVisible,
boolean isKeyguardVisibleAndOccluded,
boolean inFullImmersiveMode,
+ boolean isDragging,
@NonNull InsetsState displayInsetsState,
boolean hasGlobalFocus,
@NonNull Region displayExclusionRegion,
@@ -933,9 +935,13 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin
relayoutParams.mAsyncViewHost = isAppHandle;
final boolean showCaption;
- if (DesktopModeFlags.ENABLE_FULLY_IMMERSIVE_IN_DESKTOP.isTrue()) {
+ 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_FULLY_IMMERSIVE_IN_DESKTOP.isTrue()) {
if (inFullImmersiveMode) {
- showCaption = isStatusBarVisible && !isKeyguardVisibleAndOccluded;
+ showCaption = (isStatusBarVisible && !isKeyguardVisibleAndOccluded);
} else {
showCaption = taskInfo.isFreeform()
|| (isStatusBarVisible && !isKeyguardVisibleAndOccluded);
@@ -1405,7 +1411,7 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin
supportsMultiInstance,
shouldShowManageWindowsButton,
shouldShowChangeAspectRatioButton,
- canEnterDesktopMode(mContext),
+ isDesktopModeSupportedOnDisplay(mContext, mDisplay),
isBrowserApp,
isBrowserApp ? getAppLink() : getBrowserLink(),
mResult.mCaptionWidth,
@@ -1702,6 +1708,7 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin
mExclusionRegionListener.onExclusionRegionDismissed(mTaskInfo.taskId);
disposeResizeVeil();
disposeStatusBarInputLayer();
+ mWindowDecorViewHolder.close();
mWindowDecorViewHolder = null;
if (canEnterDesktopMode(mContext) && isEducationEnabled()) {
notifyNoCaptionHandle();
@@ -1798,6 +1805,13 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin
}
/**
+ * Declares whether the window decoration is being dragged.
+ */
+ void setIsDragging(boolean isDragging) {
+ mIsDragging = isDragging;
+ }
+
+ /**
* Called when there is a {@link MotionEvent#ACTION_HOVER_EXIT} on the maximize window button.
*/
void onMaximizeButtonHoverExit() {
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/HandleMenu.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/HandleMenu.kt
index ad2e23cb4028..c544468f5191 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
@@ -471,32 +470,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 +700,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 +733,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/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/WindowDecoration.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecoration.java
index 7baef2b2dc97..bde46a1bc375 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()) {
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..c3d15df6eae5 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
@@ -133,10 +133,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 +147,10 @@ class DesktopTilingWindowDecoration(
resizeMetadata.getLeash(),
startBounds = currentBounds,
endBounds = destinationBounds,
+ callback,
)
}
}
- initTilingForDisplayIfNeeded(taskInfo.configuration, isFirstTiledApp)
return isTiled
}
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..d9df899f8b40 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
@@ -274,4 +274,6 @@ internal class AppHandleViewHolder(
}
animator.start()
}
+
+ override fun close() {}
}
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 4c443d7501f7..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 =
@@ -498,9 +548,9 @@ class DesktopModeFlickerScenarios {
it.remove(VisibleLayersShownMoreThanOneConsecutiveEntry())
} +
listOf(
- AppWindowOnTopAtStart(DESKTOP_MODE_APP),
AppWindowBecomesInvisible(DESKTOP_MODE_APP),
AppWindowOnTopAtEnd(LAUNCHER),
+ AppWindowIsInvisibleAtEnd(DESKTOP_WALLPAPER),
).associateBy({ it }, { AssertionInvocationGroup.BLOCKING })
)
val OPEN_UNLIMITED_APPS =
@@ -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/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/bubbles/BubbleTransitionsTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubbleTransitionsTest.java
index 42310caba1c6..925ca0f1638d 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
@@ -25,6 +25,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 +36,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;
@@ -138,13 +140,14 @@ public class BubbleTransitionsTest extends ShellTestCase {
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 +175,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 +188,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 +200,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 +215,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 +240,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
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/animation/ExpandedAnimationControllerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/animation/ExpandedAnimationControllerTest.java
index 60f1d271c3af..d829c6afb787 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/animation/ExpandedAnimationControllerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/animation/ExpandedAnimationControllerTest.java
@@ -104,6 +104,7 @@ public class ExpandedAnimationControllerTest extends PhysicsAnimationLayoutTestC
@Test
public void testExpansionAndCollapse() throws Exception {
expand();
+ waitForAnimation();
testBubblesInCorrectExpandedPositions();
waitForMainThread();
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..4cdb1e5b5d10 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
@@ -41,7 +41,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 +62,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 +79,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 +95,7 @@ public class PipDesktopStateTest {
mPipDesktopState = new PipDesktopState(mMockPipDisplayLayoutState,
mMockDesktopUserRepositoriesOptional,
- mMockDesktopWallpaperActivityTokenProviderOptional,
+ mMockDragToDesktopTransitionHandlerOptional,
mMockRootTaskDisplayAreaOrganizer);
}
@@ -110,8 +112,8 @@ 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());
}
@@ -123,59 +125,7 @@ public class PipDesktopStateTest {
}
@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() {
- when(mMockDesktopRepository.isAnyDeskActive(DISPLAY_ID)).thenReturn(true);
- when(mMockDesktopWallpaperActivityTokenProvider.isWallpaperActivityVisible(
- DISPLAY_ID)).thenReturn(true);
-
- assertFalse(mPipDesktopState.shouldExitPipExitDesktopMode());
- }
-
- @Test
- public void shouldExitPipExitDesktopMode_visibleCountZero_wallpaperVisible_returnsTrue() {
- when(mMockDesktopRepository.isAnyDeskActive(DISPLAY_ID)).thenReturn(false);
- when(mMockDesktopWallpaperActivityTokenProvider.isWallpaperActivityVisible(
- DISPLAY_ID)).thenReturn(true);
-
- assertTrue(mPipDesktopState.shouldExitPipExitDesktopMode());
- }
-
- @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 +134,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 +147,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/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..275d7b73a112 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,15 +54,18 @@ import org.mockito.quality.Strictness
*
* Usage: atest WMShellUnitTests:DesktopDisplayEventHandlerTest
*/
+@OptIn(ExperimentalCoroutinesApi::class)
@SmallTest
@RunWith(AndroidTestingRunner::class)
class DesktopDisplayEventHandlerTest : ShellTestCase() {
@Mock lateinit var testExecutor: ShellExecutor
@Mock lateinit var displayController: DisplayController
@Mock private lateinit var mockDesktopUserRepositories: DesktopUserRepositories
+ @Mock private lateinit var mockDesktopRepositoryInitializer: DesktopRepositoryInitializer
@Mock private lateinit var mockDesktopRepository: DesktopRepository
@Mock private lateinit var mockDesktopTasksController: DesktopTasksController
@Mock private lateinit var desktopDisplayModeController: DesktopDisplayModeController
+ 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,
+ mockDesktopRepositoryInitializer,
mockDesktopUserRepositories,
mockDesktopTasksController,
desktopDisplayModeController,
@@ -89,17 +102,66 @@ 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 {
+ val stateFlow = MutableStateFlow(false)
+ whenever(mockDesktopRepositoryInitializer.isInitialized).thenReturn(stateFlow)
+ whenever(DesktopModeStatus.canEnterDesktopMode(context)).thenReturn(true)
- onDisplaysChangedListenerCaptor.lastValue.onDisplayAdded(DEFAULT_DISPLAY)
+ onDisplaysChangedListenerCaptor.lastValue.onDisplayAdded(DEFAULT_DISPLAY)
+ stateFlow.emit(true)
+ runCurrent()
- verify(mockDesktopTasksController).createDesk(DEFAULT_DISPLAY)
- }
+ verify(mockDesktopTasksController).createDesk(DEFAULT_DISPLAY)
+ }
+
+ @Test
+ fun testDisplayAdded_supportsDesks_desktopRepositoryNotInitialized_doesNotCreateDesk() =
+ testScope.runTest {
+ val stateFlow = MutableStateFlow(false)
+ whenever(mockDesktopRepositoryInitializer.isInitialized).thenReturn(stateFlow)
+ 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 {
+ val stateFlow = MutableStateFlow(false)
+ whenever(mockDesktopRepositoryInitializer.isInitialized).thenReturn(stateFlow)
+ whenever(DesktopModeStatus.canEnterDesktopMode(context)).thenReturn(true)
+
+ onDisplaysChangedListenerCaptor.lastValue.onDisplayAdded(DEFAULT_DISPLAY)
+ stateFlow.emit(true)
+ stateFlow.emit(true)
+ runCurrent()
+
+ verify(mockDesktopTasksController, times(1)).createDesk(DEFAULT_DISPLAY)
+ }
+
+ @Test
+ fun testDisplayAdded_supportsDesks_desktopRepositoryInitialized_deskExists_doesNotCreateDesk() =
+ testScope.runTest {
+ val stateFlow = MutableStateFlow(false)
+ whenever(mockDesktopRepositoryInitializer.isInitialized).thenReturn(stateFlow)
+ whenever(DesktopModeStatus.canEnterDesktopMode(context)).thenReturn(true)
+ whenever(mockDesktopRepository.getNumberOfDesks(DEFAULT_DISPLAY)).thenReturn(1)
+
+ onDisplaysChangedListenerCaptor.lastValue.onDisplayAdded(DEFAULT_DISPLAY)
+ stateFlow.emit(true)
+ runCurrent()
+
+ verify(mockDesktopTasksController, never()).createDesk(DEFAULT_DISPLAY)
+ }
@Test
fun testDisplayAdded_cannotEnterDesktopMode_doesNotCreateDesk() {
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 d054de4d6fc1..cc37c440f650 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
@@ -26,7 +26,6 @@ import android.platform.test.annotations.DisableFlags
import android.platform.test.annotations.EnableFlags
import android.provider.Settings
import android.provider.Settings.Global.DEVELOPMENT_FORCE_DESKTOP_MODE_ON_EXTERNAL_DISPLAYS
-import android.testing.AndroidTestingRunner
import android.view.Display.DEFAULT_DISPLAY
import android.view.IWindowManager
import android.view.WindowManager.TRANSIT_CHANGE
@@ -43,6 +42,8 @@ import com.android.wm.shell.TestRunningTaskInfoBuilder
import com.android.wm.shell.desktopmode.desktopwallpaperactivity.DesktopWallpaperActivityTokenProvider
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 org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
@@ -62,7 +63,7 @@ import org.mockito.kotlin.whenever
* Usage: atest WMShellUnitTests:DesktopDisplayModeControllerTest
*/
@SmallTest
-@RunWith(AndroidTestingRunner::class)
+@RunWith(TestParameterInjector::class)
class DesktopDisplayModeControllerTest : ShellTestCase() {
private val transitions = mock<Transitions>()
private val rootTaskDisplayAreaOrganizer = mock<RootTaskDisplayAreaOrganizer>()
@@ -143,41 +144,24 @@ class DesktopDisplayModeControllerTest : ShellTestCase() {
@Test
@DisableFlags(Flags.FLAG_ENABLE_DISPLAY_WINDOWING_MODE_SWITCHING)
- fun displayWindowingModeSwitchOnDisplayConnected_fullscreenDisplay_flagDisabled() {
- testDisplayWindowingModeSwitch(
- defaultWindowingMode = WINDOWING_MODE_FULLSCREEN,
- extendedDisplayEnabled = true,
- expectToSwitch = false,
- )
- }
-
- @Test
- @EnableFlags(Flags.FLAG_ENABLE_DISPLAY_WINDOWING_MODE_SWITCHING)
- fun displayWindowingModeSwitchOnDisplayConnected_extendedDisplayDisabled() {
+ fun displayWindowingModeSwitchOnDisplayConnected_flagDisabled(
+ @TestParameter param: ModeSwitchTestCase
+ ) {
testDisplayWindowingModeSwitch(
- defaultWindowingMode = WINDOWING_MODE_FULLSCREEN,
- extendedDisplayEnabled = false,
+ param.defaultWindowingMode,
+ param.extendedDisplayEnabled,
+ // When the flag is disabled, never switch.
expectToSwitch = false,
)
}
@Test
@EnableFlags(Flags.FLAG_ENABLE_DISPLAY_WINDOWING_MODE_SWITCHING)
- fun displayWindowingModeSwitchOnDisplayConnected_fullscreenDisplay() {
+ fun displayWindowingModeSwitchOnDisplayConnected(@TestParameter param: ModeSwitchTestCase) {
testDisplayWindowingModeSwitch(
- defaultWindowingMode = WINDOWING_MODE_FULLSCREEN,
- extendedDisplayEnabled = true,
- expectToSwitch = true,
- )
- }
-
- @Test
- @EnableFlags(Flags.FLAG_ENABLE_DISPLAY_WINDOWING_MODE_SWITCHING)
- fun displayWindowingModeSwitchOnDisplayConnected_freeformDisplay() {
- testDisplayWindowingModeSwitch(
- defaultWindowingMode = WINDOWING_MODE_FREEFORM,
- extendedDisplayEnabled = true,
- expectToSwitch = false,
+ param.defaultWindowingMode,
+ param.extendedDisplayEnabled,
+ param.expectToSwitchByDefault,
)
}
@@ -249,7 +233,34 @@ class DesktopDisplayModeControllerTest : ShellTestCase() {
}
}
- private companion object {
+ companion object {
const val EXTERNAL_DISPLAY_ID = 100
+
+ enum class ModeSwitchTestCase(
+ val defaultWindowingMode: Int,
+ val extendedDisplayEnabled: Boolean,
+ val expectToSwitchByDefault: Boolean,
+ ) {
+ FULLSCREEN_DISPLAY(
+ defaultWindowingMode = WINDOWING_MODE_FULLSCREEN,
+ extendedDisplayEnabled = true,
+ expectToSwitchByDefault = true,
+ ),
+ FULLSCREEN_DISPLAY_MIRRORING(
+ defaultWindowingMode = WINDOWING_MODE_FULLSCREEN,
+ extendedDisplayEnabled = false,
+ expectToSwitchByDefault = false,
+ ),
+ FREEFORM_DISPLAY(
+ defaultWindowingMode = WINDOWING_MODE_FREEFORM,
+ extendedDisplayEnabled = true,
+ expectToSwitchByDefault = false,
+ ),
+ FREEFORM_DISPLAY_MIRRORING(
+ defaultWindowingMode = WINDOWING_MODE_FREEFORM,
+ extendedDisplayEnabled = false,
+ expectToSwitchByDefault = false,
+ ),
+ }
}
}
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/DesktopMinimizationTransitionHandlerTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopMinimizationTransitionHandlerTest.kt
index 0d1c57221fb9..3e6f688e6acc 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopMinimizationTransitionHandlerTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopMinimizationTransitionHandlerTest.kt
@@ -33,6 +33,7 @@ import com.android.wm.shell.ShellTestCase
import com.android.wm.shell.TestRunningTaskInfoBuilder
import com.android.wm.shell.common.DisplayController
import com.android.wm.shell.common.ShellExecutor
+import com.android.wm.shell.transition.Transitions
import org.junit.Assert.assertFalse
import org.junit.Assert.assertNull
import org.junit.Assert.assertTrue
@@ -154,6 +155,24 @@ class DesktopMinimizationTransitionHandlerTest : ShellTestCase() {
assertTrue("Should animate going to back freeform task close transition", animates)
}
+ @Test
+ fun startAnimation_minimizeTransitionToBackFreeformTask_returnsTrue() {
+ val animates =
+ handler.startAnimation(
+ transition = mock(),
+ info =
+ createTransitionInfo(
+ type = Transitions.TRANSIT_MINIMIZE,
+ task = createTask(WINDOWING_MODE_FREEFORM),
+ ),
+ startTransaction = mock(),
+ finishTransaction = mock(),
+ finishCallback = {},
+ )
+
+ assertTrue("Should animate going to back freeform task minimize transition", animates)
+ }
+
private fun createTransitionInfo(
type: Int = WindowManager.TRANSIT_TO_BACK,
changeMode: Int = WindowManager.TRANSIT_TO_BACK,
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/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/DesktopRepositoryTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopRepositoryTest.kt
index a43b4dd00911..f84a1a38bdfc 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()
@@ -1172,6 +1171,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 +1185,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 +1198,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)
@@ -1242,39 +1255,6 @@ class DesktopRepositoryTest(flags: FlagsParameterization) : ShellTestCase() {
}
@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 +1438,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 +1455,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 +1471,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 50590f021a2a..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
@@ -56,7 +56,7 @@ class DesktopTaskChangeListenerTest : ShellTestCase() {
}
@Test
- fun onTaskOpening_fullscreenTask_notActiveDesktopTask_noop() {
+ fun onTaskOpening_fullscreenTask_nonActiveDesktopTask_noop() {
val task = createFullscreenTask().apply { isVisible = true }
whenever(desktopUserRepositories.current.isActiveTask(task.taskId)).thenReturn(false)
@@ -68,7 +68,7 @@ class DesktopTaskChangeListenerTest : ShellTestCase() {
}
@Test
- fun onTaskOpening_freeformTask_activeDesktopTask_removesTaskFromRepo() {
+ fun onTaskOpening_fullscreenTask_taskIsActiveInDesktopRepo_removesTaskFromDesktopRepo() {
val task = createFullscreenTask().apply { isVisible = true }
whenever(desktopUserRepositories.current.isActiveTask(task.taskId)).thenReturn(true)
@@ -78,19 +78,20 @@ class DesktopTaskChangeListenerTest : ShellTestCase() {
}
@Test
- fun onTaskOpening_freeformTask_visibleDesktopTask_addsTaskToRepository() {
+ fun onTaskOpening_freeformTask_activeInDesktopRepository_noop() {
val task = createFreeformTask().apply { isVisible = true }
- whenever(desktopUserRepositories.current.isActiveTask(task.taskId)).thenReturn(false)
+ whenever(desktopUserRepositories.current.isActiveTask(task.taskId)).thenReturn(true)
desktopTaskChangeListener.onTaskOpening(task)
- verify(desktopUserRepositories.current).addTask(task.displayId, task.taskId, task.isVisible)
+ verify(desktopUserRepositories.current, never())
+ .addTask(task.displayId, task.taskId, task.isVisible)
}
@Test
- fun onTaskOpening_freeformTask_nonVisibleDesktopTask_addsTaskToRepository() {
+ fun onTaskOpening_freeformTask_notActiveInDesktopRepo_addsTaskToRepository() {
val task = createFreeformTask().apply { isVisible = false }
- whenever(desktopUserRepositories.current.isActiveTask(task.taskId)).thenReturn(true)
+ whenever(desktopUserRepositories.current.isActiveTask(task.taskId)).thenReturn(false)
desktopTaskChangeListener.onTaskOpening(task)
@@ -98,7 +99,7 @@ class DesktopTaskChangeListenerTest : ShellTestCase() {
}
@Test
- fun onTaskChanging_freeformTaskOutsideDesktop_removesTaskFromRepo() {
+ fun onTaskChanging_fullscreenTask_activeInDesktopRepository_removesTaskFromRepo() {
val task = createFullscreenTask().apply { isVisible = true }
whenever(desktopUserRepositories.current.isActiveTask(task.taskId)).thenReturn(true)
@@ -108,29 +109,26 @@ class DesktopTaskChangeListenerTest : ShellTestCase() {
}
@Test
- fun onTaskChanging_visibleTaskInDesktop_updatesTaskVisibility() {
- val task = createFreeformTask().apply { isVisible = true }
- whenever(desktopUserRepositories.current.isActiveTask(task.taskId)).thenReturn(true)
+ fun onTaskChanging_fullscreenTask_nonActiveInDesktopRepo_noop() {
+ val task = createFullscreenTask().apply { isVisible = true }
+ whenever(desktopUserRepositories.current.isActiveTask(task.taskId)).thenReturn(false)
desktopTaskChangeListener.onTaskChanging(task)
- verify(desktopUserRepositories.current)
- .updateTask(task.displayId, task.taskId, task.isVisible)
+ verify(desktopUserRepositories.current, never()).removeTask(task.displayId, task.taskId)
}
@Test
- fun onTaskChanging_nonVisibleTask_updatesTaskVisibility() {
- val task = createFreeformTask().apply { isVisible = false }
- whenever(desktopUserRepositories.current.isActiveTask(task.taskId)).thenReturn(true)
+ fun onTaskChanging_freeformTask_addsTaskToDesktopRepo() {
+ val task = createFreeformTask().apply { isVisible = true }
desktopTaskChangeListener.onTaskChanging(task)
- verify(desktopUserRepositories.current)
- .updateTask(task.displayId, task.taskId, task.isVisible)
+ verify(desktopUserRepositories.current).addTask(task.displayId, task.taskId, task.isVisible)
}
@Test
- fun onTaskMovingToFront_freeformTaskOutsideDesktop_removesTaskFromRepo() {
+ fun onTaskMovingToFront_fullscreenTask_activeTaskInDesktopRepo_removesTaskFromRepo() {
val task = createFullscreenTask().apply { isVisible = true }
whenever(desktopUserRepositories.current.isActiveTask(task.taskId)).thenReturn(true)
@@ -140,9 +138,18 @@ class DesktopTaskChangeListenerTest : ShellTestCase() {
}
@Test
- fun onTaskMovingToFront_freeformTaskOutsideDesktop_addsTaskToRepo() {
+ fun onTaskMovingToFront_fullscreenTask_nonActiveTaskInDesktopRepo_noop() {
val task = createFullscreenTask().apply { isVisible = true }
- whenever(desktopUserRepositories.current.isActiveTask(task.taskId)).thenReturn(true)
+ whenever(desktopUserRepositories.current.isActiveTask(task.taskId)).thenReturn(false)
+
+ desktopTaskChangeListener.onTaskMovingToFront(task)
+
+ verify(desktopUserRepositories.current, never()).removeTask(task.displayId, task.taskId)
+ }
+
+ @Test
+ fun onTaskMovingToFront_freeformTask_addsTaskToRepo() {
+ val task = createFreeformTask().apply { isVisible = true }
desktopTaskChangeListener.onTaskMovingToFront(task)
@@ -150,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 d5c84bafbd81..34c5ebd6d94d 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
@@ -87,7 +89,6 @@ import com.android.dx.mockito.inline.extended.StaticMockitoSession
import com.android.internal.jank.InteractionJankMonitor
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 +103,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
@@ -264,6 +266,8 @@ class DesktopTasksControllerTest(flags: FlagsParameterization) : ShellTestCase()
@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 +278,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 +298,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,18 +331,30 @@ 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() }
whenever(enterDesktopTransitionHandler.moveToDesktop(any(), any())).thenAnswer { Binder() }
whenever(exitDesktopTransitionHandler.startTransition(any(), any(), any(), any()))
.thenReturn(Binder())
+ whenever(
+ desktopMixedTransitionHandler.startLaunchTransition(
+ any(),
+ any(),
+ anyOrNull(),
+ anyOrNull(),
+ anyOrNull(),
+ )
+ )
+ .thenReturn(Binder())
whenever(displayController.getDisplayLayout(anyInt())).thenReturn(displayLayout)
whenever(displayController.getDisplayContext(anyInt())).thenReturn(mockDisplayContext)
whenever(displayController.getDisplay(anyInt())).thenReturn(display)
@@ -417,6 +438,7 @@ class DesktopTasksControllerTest(flags: FlagsParameterization) : ShellTestCase()
dragToDesktopTransitionHandler,
mMockDesktopImmersiveController,
userRepositories,
+ repositoryInitializer,
recentsTransitionHandler,
multiInstanceHelper,
shellExecutor,
@@ -436,6 +458,8 @@ class DesktopTasksControllerTest(flags: FlagsParameterization) : ShellTestCase()
userProfileContexts,
desktopModeCompatPolicy,
dragToDisplayTransitionHandler,
+ moveToDisplayTransitionHandler,
+ homeIntentProvider,
)
@After
@@ -609,11 +633,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()
@@ -624,6 +650,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()
@@ -638,6 +673,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)
@@ -646,38 +682,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,
@@ -1067,11 +1085,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()
@@ -1082,6 +1118,7 @@ class DesktopTasksControllerTest(flags: FlagsParameterization) : ShellTestCase()
}
@Test
+ @DisableFlags(Flags.FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND)
fun isInDesktop_twoTasks_oneVisible_returnsTrue() {
setUpHomeTask()
@@ -1092,6 +1129,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)
@@ -1104,47 +1142,95 @@ class DesktopTasksControllerTest(flags: FlagsParameterization) : ShellTestCase()
}
@Test
- fun addMoveToDesktopChanges_gravityLeft_noBoundsApplied() {
+ fun addMoveToDeskTaskChanges_gravityLeft_noBoundsApplied() {
setUpLandscapeDisplay()
val task = setUpFullscreenTask(gravity = Gravity.LEFT)
val wct = WindowContainerTransaction()
- controller.addMoveToDesktopChanges(wct, task)
+ controller.addMoveToDeskTaskChanges(wct, task, deskId = 0)
val finalBounds = findBoundsChange(wct, task)
- assertThat(finalBounds).isEqualTo(Rect())
+ assertThat(finalBounds).isNull()
}
@Test
- fun addMoveToDesktopChanges_gravityRight_noBoundsApplied() {
+ fun addMoveToDeskTaskChanges_gravityRight_noBoundsApplied() {
setUpLandscapeDisplay()
val task = setUpFullscreenTask(gravity = Gravity.RIGHT)
val wct = WindowContainerTransaction()
- controller.addMoveToDesktopChanges(wct, task)
+ controller.addMoveToDeskTaskChanges(wct, task, deskId = 0)
val finalBounds = findBoundsChange(wct, task)
- assertThat(finalBounds).isEqualTo(Rect())
+ assertThat(finalBounds).isNull()
}
@Test
- fun addMoveToDesktopChanges_gravityTop_noBoundsApplied() {
+ fun addMoveToDeskTaskChanges_gravityTop_noBoundsApplied() {
setUpLandscapeDisplay()
val task = setUpFullscreenTask(gravity = Gravity.TOP)
val wct = WindowContainerTransaction()
- controller.addMoveToDesktopChanges(wct, task)
+ controller.addMoveToDeskTaskChanges(wct, task, deskId = 0)
val finalBounds = findBoundsChange(wct, task)
- assertThat(finalBounds).isEqualTo(Rect())
+ assertThat(finalBounds).isNull()
}
@Test
- fun addMoveToDesktopChanges_gravityBottom_noBoundsApplied() {
+ fun addMoveToDeskTaskChanges_gravityBottom_noBoundsApplied() {
setUpLandscapeDisplay()
val task = setUpFullscreenTask(gravity = Gravity.BOTTOM)
val wct = WindowContainerTransaction()
- controller.addMoveToDesktopChanges(wct, task)
+ 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
@@ -1182,7 +1268,7 @@ class DesktopTasksControllerTest(flags: FlagsParameterization) : ShellTestCase()
@Test
@EnableFlags(Flags.FLAG_ENABLE_CASCADING_WINDOWS)
- fun addMoveToDesktopChanges_positionBottomRight() {
+ fun addMoveToDeskTaskChanges_positionBottomRight() {
setUpLandscapeDisplay()
val stableBounds = Rect()
displayLayout.getStableBoundsForDesktopMode(stableBounds)
@@ -1191,7 +1277,7 @@ class DesktopTasksControllerTest(flags: FlagsParameterization) : ShellTestCase()
val task = setUpFullscreenTask()
val wct = WindowContainerTransaction()
- controller.addMoveToDesktopChanges(wct, task)
+ controller.addMoveToDeskTaskChanges(wct, task, deskId = 0)
val finalBounds = findBoundsChange(wct, task)
assertThat(stableBounds.getDesktopTaskPosition(finalBounds!!))
@@ -1200,7 +1286,7 @@ class DesktopTasksControllerTest(flags: FlagsParameterization) : ShellTestCase()
@Test
@EnableFlags(Flags.FLAG_ENABLE_CASCADING_WINDOWS)
- fun addMoveToDesktopChanges_positionTopLeft() {
+ fun addMoveToDeskTaskChanges_positionTopLeft() {
setUpLandscapeDisplay()
val stableBounds = Rect()
displayLayout.getStableBoundsForDesktopMode(stableBounds)
@@ -1209,7 +1295,7 @@ class DesktopTasksControllerTest(flags: FlagsParameterization) : ShellTestCase()
val task = setUpFullscreenTask()
val wct = WindowContainerTransaction()
- controller.addMoveToDesktopChanges(wct, task)
+ controller.addMoveToDeskTaskChanges(wct, task, deskId = 0)
val finalBounds = findBoundsChange(wct, task)
assertThat(stableBounds.getDesktopTaskPosition(finalBounds!!))
@@ -1218,7 +1304,7 @@ class DesktopTasksControllerTest(flags: FlagsParameterization) : ShellTestCase()
@Test
@EnableFlags(Flags.FLAG_ENABLE_CASCADING_WINDOWS)
- fun addMoveToDesktopChanges_positionBottomLeft() {
+ fun addMoveToDeskTaskChanges_positionBottomLeft() {
setUpLandscapeDisplay()
val stableBounds = Rect()
displayLayout.getStableBoundsForDesktopMode(stableBounds)
@@ -1227,7 +1313,7 @@ class DesktopTasksControllerTest(flags: FlagsParameterization) : ShellTestCase()
val task = setUpFullscreenTask()
val wct = WindowContainerTransaction()
- controller.addMoveToDesktopChanges(wct, task)
+ controller.addMoveToDeskTaskChanges(wct, task, deskId = 0)
val finalBounds = findBoundsChange(wct, task)
assertThat(stableBounds.getDesktopTaskPosition(finalBounds!!))
@@ -1236,7 +1322,7 @@ class DesktopTasksControllerTest(flags: FlagsParameterization) : ShellTestCase()
@Test
@EnableFlags(Flags.FLAG_ENABLE_CASCADING_WINDOWS)
- fun addMoveToDesktopChanges_positionTopRight() {
+ fun addMoveToDeskTaskChanges_positionTopRight() {
setUpLandscapeDisplay()
val stableBounds = Rect()
displayLayout.getStableBoundsForDesktopMode(stableBounds)
@@ -1245,7 +1331,7 @@ class DesktopTasksControllerTest(flags: FlagsParameterization) : ShellTestCase()
val task = setUpFullscreenTask()
val wct = WindowContainerTransaction()
- controller.addMoveToDesktopChanges(wct, task)
+ controller.addMoveToDeskTaskChanges(wct, task, deskId = 0)
val finalBounds = findBoundsChange(wct, task)
assertThat(stableBounds.getDesktopTaskPosition(finalBounds!!))
@@ -1254,7 +1340,7 @@ class DesktopTasksControllerTest(flags: FlagsParameterization) : ShellTestCase()
@Test
@EnableFlags(Flags.FLAG_ENABLE_CASCADING_WINDOWS)
- fun addMoveToDesktopChanges_positionResetsToCenter() {
+ fun addMoveToDeskTaskChanges_positionResetsToCenter() {
setUpLandscapeDisplay()
val stableBounds = Rect()
displayLayout.getStableBoundsForDesktopMode(stableBounds)
@@ -1263,7 +1349,7 @@ class DesktopTasksControllerTest(flags: FlagsParameterization) : ShellTestCase()
val task = setUpFullscreenTask()
val wct = WindowContainerTransaction()
- controller.addMoveToDesktopChanges(wct, task)
+ controller.addMoveToDeskTaskChanges(wct, task, deskId = 0)
val finalBounds = findBoundsChange(wct, task)
assertThat(stableBounds.getDesktopTaskPosition(finalBounds!!))
@@ -1272,7 +1358,7 @@ class DesktopTasksControllerTest(flags: FlagsParameterization) : ShellTestCase()
@Test
@EnableFlags(Flags.FLAG_ENABLE_CASCADING_WINDOWS)
- fun addMoveToDesktopChanges_lastWindowSnapLeft_positionResetsToCenter() {
+ fun addMoveToDeskTaskChanges_lastWindowSnapLeft_positionResetsToCenter() {
setUpLandscapeDisplay()
val stableBounds = Rect()
displayLayout.getStableBoundsForDesktopMode(stableBounds)
@@ -1284,7 +1370,7 @@ class DesktopTasksControllerTest(flags: FlagsParameterization) : ShellTestCase()
val task = setUpFullscreenTask()
val wct = WindowContainerTransaction()
- controller.addMoveToDesktopChanges(wct, task)
+ controller.addMoveToDeskTaskChanges(wct, task, deskId = 0)
val finalBounds = findBoundsChange(wct, task)
assertThat(stableBounds.getDesktopTaskPosition(finalBounds!!))
@@ -1293,7 +1379,7 @@ class DesktopTasksControllerTest(flags: FlagsParameterization) : ShellTestCase()
@Test
@EnableFlags(Flags.FLAG_ENABLE_CASCADING_WINDOWS)
- fun addMoveToDesktopChanges_lastWindowSnapRight_positionResetsToCenter() {
+ fun addMoveToDeskTaskChanges_lastWindowSnapRight_positionResetsToCenter() {
setUpLandscapeDisplay()
val stableBounds = Rect()
displayLayout.getStableBoundsForDesktopMode(stableBounds)
@@ -1311,7 +1397,7 @@ class DesktopTasksControllerTest(flags: FlagsParameterization) : ShellTestCase()
val task = setUpFullscreenTask()
val wct = WindowContainerTransaction()
- controller.addMoveToDesktopChanges(wct, task)
+ controller.addMoveToDeskTaskChanges(wct, task, deskId = 0)
val finalBounds = findBoundsChange(wct, task)
assertThat(stableBounds.getDesktopTaskPosition(finalBounds!!))
@@ -1320,7 +1406,7 @@ class DesktopTasksControllerTest(flags: FlagsParameterization) : ShellTestCase()
@Test
@EnableFlags(Flags.FLAG_ENABLE_CASCADING_WINDOWS)
- fun addMoveToDesktopChanges_lastWindowMaximised_positionResetsToCenter() {
+ fun addMoveToDeskTaskChanges_lastWindowMaximised_positionResetsToCenter() {
setUpLandscapeDisplay()
val stableBounds = Rect()
displayLayout.getStableBoundsForDesktopMode(stableBounds)
@@ -1330,7 +1416,7 @@ class DesktopTasksControllerTest(flags: FlagsParameterization) : ShellTestCase()
val task = setUpFullscreenTask()
val wct = WindowContainerTransaction()
- controller.addMoveToDesktopChanges(wct, task)
+ controller.addMoveToDeskTaskChanges(wct, task, deskId = 0)
val finalBounds = findBoundsChange(wct, task)
assertThat(stableBounds.getDesktopTaskPosition(finalBounds!!))
@@ -1339,7 +1425,7 @@ class DesktopTasksControllerTest(flags: FlagsParameterization) : ShellTestCase()
@Test
@EnableFlags(Flags.FLAG_ENABLE_CASCADING_WINDOWS)
- fun addMoveToDesktopChanges_defaultToCenterIfFree() {
+ fun addMoveToDeskTaskChanges_defaultToCenterIfFree() {
setUpLandscapeDisplay()
val stableBounds = Rect()
displayLayout.getStableBoundsForDesktopMode(stableBounds)
@@ -1357,7 +1443,7 @@ class DesktopTasksControllerTest(flags: FlagsParameterization) : ShellTestCase()
val task = setUpFullscreenTask()
val wct = WindowContainerTransaction()
- controller.addMoveToDesktopChanges(wct, task)
+ controller.addMoveToDeskTaskChanges(wct, task, deskId = 0)
val finalBounds = findBoundsChange(wct, task)
assertThat(stableBounds.getDesktopTaskPosition(finalBounds!!))
@@ -1365,7 +1451,7 @@ class DesktopTasksControllerTest(flags: FlagsParameterization) : ShellTestCase()
}
@Test
- fun addMoveToDesktopChanges_excludeCaptionFromAppBounds_nonResizableLandscape() {
+ fun addMoveToDeskTaskChanges_excludeCaptionFromAppBounds_nonResizableLandscape() {
setUpLandscapeDisplay()
val task =
setUpFullscreenTask(
@@ -1375,7 +1461,7 @@ class DesktopTasksControllerTest(flags: FlagsParameterization) : ShellTestCase()
whenever(desktopModeCompatPolicy.shouldExcludeCaptionFromAppBounds(task)).thenReturn(true)
val initialAspectRatio = calculateAspectRatio(task)
val wct = WindowContainerTransaction()
- controller.addMoveToDesktopChanges(wct, task)
+ controller.addMoveToDeskTaskChanges(wct, task, deskId = 0)
val finalBounds = findBoundsChange(wct, task)
val captionInsets = getAppHeaderHeight(context)
@@ -1387,7 +1473,7 @@ class DesktopTasksControllerTest(flags: FlagsParameterization) : ShellTestCase()
}
@Test
- fun addMoveToDesktopChanges_excludeCaptionFromAppBounds_nonResizablePortrait() {
+ fun addMoveToDeskTaskChanges_excludeCaptionFromAppBounds_nonResizablePortrait() {
setUpLandscapeDisplay()
val task =
setUpFullscreenTask(
@@ -1397,7 +1483,7 @@ class DesktopTasksControllerTest(flags: FlagsParameterization) : ShellTestCase()
whenever(desktopModeCompatPolicy.shouldExcludeCaptionFromAppBounds(task)).thenReturn(true)
val initialAspectRatio = calculateAspectRatio(task)
val wct = WindowContainerTransaction()
- controller.addMoveToDesktopChanges(wct, task)
+ controller.addMoveToDeskTaskChanges(wct, task, deskId = 0)
val finalBounds = findBoundsChange(wct, task)
val captionInsets = getAppHeaderHeight(context)
@@ -1435,29 +1521,29 @@ class DesktopTasksControllerTest(flags: FlagsParameterization) : ShellTestCase()
@Test
@EnableFlags(Flags.FLAG_ENABLE_WINDOWING_DYNAMIC_INITIAL_BOUNDS)
- fun addMoveToDesktopChanges_landscapeDevice_userFullscreenOverride_defaultPortraitBounds() {
+ fun addMoveToDeskTaskChanges_landscapeDevice_userFullscreenOverride_defaultPortraitBounds() {
setUpLandscapeDisplay()
val task = setUpFullscreenTask(enableUserFullscreenOverride = true)
val wct = WindowContainerTransaction()
- controller.addMoveToDesktopChanges(wct, task)
+ controller.addMoveToDeskTaskChanges(wct, task, deskId = 0)
assertThat(findBoundsChange(wct, task)).isEqualTo(DEFAULT_LANDSCAPE_BOUNDS)
}
@Test
@EnableFlags(Flags.FLAG_ENABLE_WINDOWING_DYNAMIC_INITIAL_BOUNDS)
- fun addMoveToDesktopChanges_landscapeDevice_systemFullscreenOverride_defaultPortraitBounds() {
+ fun addMoveToDeskTaskChanges_landscapeDevice_systemFullscreenOverride_defaultPortraitBounds() {
setUpLandscapeDisplay()
val task = setUpFullscreenTask(enableSystemFullscreenOverride = true)
val wct = WindowContainerTransaction()
- controller.addMoveToDesktopChanges(wct, task)
+ controller.addMoveToDeskTaskChanges(wct, task, deskId = 0)
assertThat(findBoundsChange(wct, task)).isEqualTo(DEFAULT_LANDSCAPE_BOUNDS)
}
@Test
@EnableFlags(Flags.FLAG_ENABLE_WINDOWING_DYNAMIC_INITIAL_BOUNDS)
- fun addMoveToDesktopChanges_landscapeDevice_portraitResizableApp_aspectRatioOverridden() {
+ fun addMoveToDeskTaskChanges_landscapeDevice_portraitResizableApp_aspectRatioOverridden() {
setUpLandscapeDisplay()
val task =
setUpFullscreenTask(
@@ -1466,36 +1552,36 @@ class DesktopTasksControllerTest(flags: FlagsParameterization) : ShellTestCase()
aspectRatioOverrideApplied = true,
)
val wct = WindowContainerTransaction()
- controller.addMoveToDesktopChanges(wct, task)
+ controller.addMoveToDeskTaskChanges(wct, task, deskId = 0)
assertThat(findBoundsChange(wct, task)).isEqualTo(UNRESIZABLE_PORTRAIT_BOUNDS)
}
@Test
@EnableFlags(Flags.FLAG_ENABLE_WINDOWING_DYNAMIC_INITIAL_BOUNDS)
- fun addMoveToDesktopChanges_portraitDevice_userFullscreenOverride_defaultPortraitBounds() {
+ fun addMoveToDeskTaskChanges_portraitDevice_userFullscreenOverride_defaultPortraitBounds() {
setUpPortraitDisplay()
val task = setUpFullscreenTask(enableUserFullscreenOverride = true)
val wct = WindowContainerTransaction()
- controller.addMoveToDesktopChanges(wct, task)
+ controller.addMoveToDeskTaskChanges(wct, task, deskId = 0)
assertThat(findBoundsChange(wct, task)).isEqualTo(DEFAULT_PORTRAIT_BOUNDS)
}
@Test
@EnableFlags(Flags.FLAG_ENABLE_WINDOWING_DYNAMIC_INITIAL_BOUNDS)
- fun addMoveToDesktopChanges_portraitDevice_systemFullscreenOverride_defaultPortraitBounds() {
+ fun addMoveToDeskTaskChanges_portraitDevice_systemFullscreenOverride_defaultPortraitBounds() {
setUpPortraitDisplay()
val task = setUpFullscreenTask(enableSystemFullscreenOverride = true)
val wct = WindowContainerTransaction()
- controller.addMoveToDesktopChanges(wct, task)
+ controller.addMoveToDeskTaskChanges(wct, task, deskId = 0)
assertThat(findBoundsChange(wct, task)).isEqualTo(DEFAULT_PORTRAIT_BOUNDS)
}
@Test
@EnableFlags(Flags.FLAG_ENABLE_WINDOWING_DYNAMIC_INITIAL_BOUNDS)
- fun addMoveToDesktopChanges_portraitDevice_landscapeResizableApp_aspectRatioOverridden() {
+ fun addMoveToDeskTaskChanges_portraitDevice_landscapeResizableApp_aspectRatioOverridden() {
setUpPortraitDisplay()
val task =
setUpFullscreenTask(
@@ -1505,7 +1591,7 @@ class DesktopTasksControllerTest(flags: FlagsParameterization) : ShellTestCase()
aspectRatioOverrideApplied = true,
)
val wct = WindowContainerTransaction()
- controller.addMoveToDesktopChanges(wct, task)
+ controller.addMoveToDeskTaskChanges(wct, task, deskId = 0)
assertThat(findBoundsChange(wct, task)).isEqualTo(UNRESIZABLE_LANDSCAPE_BOUNDS)
}
@@ -1828,6 +1914,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(
@@ -2383,6 +2470,7 @@ class DesktopTasksControllerTest(flags: FlagsParameterization) : ShellTestCase()
}
@Test
+ @DisableFlags(Flags.FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND)
fun moveTaskToFront_postsWctWithReorderOp() {
val task1 = setUpFreeformTask()
setUpFreeformTask()
@@ -2405,9 +2493,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),
@@ -2428,6 +2541,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()
@@ -2516,8 +2655,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(
@@ -2540,15 +2684,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)
@@ -2562,16 +2734,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))
@@ -2585,15 +2780,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,
@@ -2612,7 +2829,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)
@@ -2638,9 +2860,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)
}
@@ -2649,6 +2874,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
@@ -2672,7 +2898,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
@@ -2685,6 +2916,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
@@ -2709,7 +2941,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))
@@ -2721,6 +2958,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))
@@ -2740,7 +2978,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)
@@ -2778,7 +3021,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)
@@ -2805,9 +3053,14 @@ 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()
@@ -2815,7 +3068,7 @@ class DesktopTasksControllerTest(flags: FlagsParameterization) : ShellTestCase()
@Test
@DisableFlags(Flags.FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND)
- fun moveToNextDisplay_toDesktopInOtherDisplay_bringsExistingTasksToFront() {
+ fun moveToNextDisplay_toDesktopInOtherDisplay_multiDesksDisabled_bringsExistingTasksToFront() {
val transition = Binder()
val sourceDeskId = 0
val targetDeskId = 2
@@ -2841,12 +3094,39 @@ 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_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY,
Flags.FLAG_ENABLE_PER_DISPLAY_DESKTOP_WALLPAPER_ACTIVITY,
Flags.FLAG_ENABLE_DESKTOP_WALLPAPER_ACTIVITY_FOR_SYSTEM_USER,
)
- @DisableFlags(Flags.FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND)
fun moveToNextDisplay_toDesktopInOtherDisplay_movesHomeAndWallpaperToFront() {
val homeTask = setUpHomeTask(displayId = SECOND_DISPLAY)
whenever(desktopWallpaperActivityTokenProvider.getToken(SECOND_DISPLAY))
@@ -2910,11 +3190,10 @@ class DesktopTasksControllerTest(flags: FlagsParameterization) : ShellTestCase()
verify(desksOrganizer).activateDesk(any(), eq(targetDeskId))
verify(desksTransitionsObserver)
.addPendingTransition(
- DeskTransition.ActiveDeskWithTask(
+ DeskTransition.ActivateDesk(
token = transition,
displayId = SECOND_DISPLAY,
deskId = targetDeskId,
- enterTaskId = task.taskId,
)
)
}
@@ -2956,7 +3235,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)
@@ -3095,42 +3374,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()
@@ -3259,26 +3502,6 @@ class DesktopTasksControllerTest(flags: FlagsParameterization) : ShellTestCase()
}
@Test
- fun onPipTaskMinimize_doesntRemoveWallpaper() {
- 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)
-
- 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()
- }
-
- @Test
fun onDesktopWindowMinimize_singleActiveTask_noWallpaperActivityToken_doesntRemoveWallpaper() {
val task = setUpFreeformTask(active = true)
val transition = Binder()
@@ -3496,7 +3719,41 @@ class DesktopTasksControllerTest(flags: FlagsParameterization) : ShellTestCase()
}
@Test
- fun handleRequest_fullscreenTask_freeformVisible_returnSwitchToFreeformWCT() {
+ @EnableFlags(Flags.FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND)
+ fun handleRequest_fullscreenTask_switchToDesktop_movesTaskToDesk() {
+ taskRepository.addDesk(displayId = DEFAULT_DISPLAY, deskId = 5)
+ setUpFreeformTask(displayId = DEFAULT_DISPLAY, deskId = 5)
+ taskRepository.setActiveDesk(displayId = DEFAULT_DISPLAY, deskId = 5)
+
+ val fullscreenTask = createFullscreenTask()
+ val wct = controller.handleRequest(Binder(), createTransition(fullscreenTask))
+
+ assertNotNull(wct, "should handle request")
+ verify(desksOrganizer).moveTaskToDesk(wct = wct, deskId = 5, task = fullscreenTask)
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND)
+ fun handleRequest_fullscreenTaskThatWasInactiveInDesk_tracksDeskDeactivation() {
+ // Set up and existing desktop task in an active desk.
+ val inactiveInDeskTask = setUpFreeformTask(displayId = DEFAULT_DISPLAY, deskId = 0)
+ taskRepository.setDeskInactive(deskId = 0)
+
+ // Now the task is launching as fullscreen.
+ inactiveInDeskTask.configuration.windowConfiguration.windowingMode =
+ WINDOWING_MODE_FULLSCREEN
+ val transition = Binder()
+ val wct = controller.handleRequest(transition, createTransition(inactiveInDeskTask))
+
+ // Desk is deactivated.
+ assertNotNull(wct, "should handle request")
+ verify(desksTransitionsObserver)
+ .addPendingTransition(DeskTransition.DeactivateDesk(transition, deskId = 0))
+ }
+
+ @Test
+ @DisableFlags(Flags.FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND)
+ fun handleRequest_fullscreenTask_freeformVisible_multiDesksDisabled_returnSwitchToFreeformWCT() {
val homeTask = setUpHomeTask()
val freeformTask = setUpFreeformTask()
markTaskVisible(freeformTask)
@@ -3512,7 +3769,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)
@@ -3537,7 +3809,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()
@@ -3550,7 +3838,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()
@@ -3564,7 +3874,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()
@@ -3573,13 +3910,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
- fun handleRequest_fullscreenTaskWithTaskOnHome_beyondLimit_existingAndNewTasksAreMinimized() {
+ @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
+ @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() }
@@ -3590,16 +3957,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)
@@ -3622,7 +4078,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
@@ -3634,6 +4111,7 @@ class DesktopTasksControllerTest(flags: FlagsParameterization) : ShellTestCase()
}
@Test
+ @DisableFlags(Flags.FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND)
fun handleRequest_fullscreenTask_freeformNotVisible_returnNull() {
val freeformTask = setUpFreeformTask()
markTaskHidden(freeformTask)
@@ -3642,12 +4120,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)
@@ -3658,8 +4147,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()
@@ -3671,8 +4179,41 @@ 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)
+ taskRepository.setDeskInactive(deskId = deskId)
+
+ val transition = Binder()
+ controller.handleRequest(transition, createTransition(freeformTask))
+
+ verify(desksTransitionsObserver)
+ .addPendingTransition(DeskTransition.DeactivateDesk(transition, deskId))
+ }
+
+ @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))
@@ -3702,9 +4243,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")
@@ -3713,7 +4255,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()
@@ -3732,7 +4277,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()
@@ -3755,34 +4301,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
@@ -3798,10 +4357,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))
@@ -3918,8 +4480,27 @@ class DesktopTasksControllerTest(flags: FlagsParameterization) : ShellTestCase()
}
@Test
+ @EnableFlags(Flags.FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND)
+ fun handleRequest_recentsAnimationRunning_relaunchActiveTask_tracksDeskDeactivation() {
+ // Set up a visible freeform task
+ val freeformTask = setUpFreeformTask(displayId = DEFAULT_DISPLAY, deskId = 0)
+ markTaskVisible(freeformTask)
+
+ // Mark recents animation running
+ recentsTransitionStateListener.onTransitionStateChanged(TRANSITION_STATE_ANIMATING)
+
+ val transition = Binder()
+ controller.handleRequest(transition, createTransition(freeformTask))
+
+ desksTransitionsObserver.addPendingTransition(
+ DeskTransition.DeactivateDesk(transition, deskId = 0)
+ )
+ }
+
+ @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)
@@ -3936,6 +4517,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() {
@@ -3994,7 +4598,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)
@@ -4006,8 +4611,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()
@@ -4035,8 +4660,34 @@ class DesktopTasksControllerTest(flags: FlagsParameterization) : ShellTestCase()
}
@Test
+ @EnableFlags(
+ Flags.FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND,
+ Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MODALS_POLICY,
+ )
+ fun handleRequest_systemUIActivityWithDisplayInFreeformTask_inDesktop_tracksDeskDeactivation() {
+ val deskId = 5
+ taskRepository.addDesk(displayId = DEFAULT_DISPLAY, deskId = deskId)
+ taskRepository.setActiveDesk(displayId = DEFAULT_DISPLAY, deskId = deskId)
+ val systemUIPackageName =
+ context.resources.getString(com.android.internal.R.string.config_systemUi)
+ val baseComponent = ComponentName(systemUIPackageName, /* cls= */ "")
+ val task =
+ setUpFreeformTask(displayId = DEFAULT_DISPLAY).apply {
+ baseActivity = baseComponent
+ isTopActivityNoDisplay = false
+ }
+
+ val transition = Binder()
+ controller.handleRequest(transition, createTransition(task))
+
+ verify(desksTransitionsObserver)
+ .addPendingTransition(DeskTransition.DeactivateDesk(transition, deskId))
+ }
+
+ @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)
@@ -4055,6 +4706,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()
@@ -4097,6 +4774,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
@@ -4637,32 +5315,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() {
@@ -4756,6 +5408,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)
@@ -5270,7 +5971,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(
@@ -5541,7 +6243,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()
@@ -5555,7 +6258,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)
@@ -5564,10 +6269,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()
@@ -5593,6 +6330,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()
@@ -5755,7 +6526,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)
@@ -5852,7 +6624,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()
}
@@ -6408,6 +7181,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,
@@ -6484,6 +7258,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(
@@ -6519,6 +7294,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(
@@ -6548,7 +7324,8 @@ class DesktopTasksControllerTest(flags: FlagsParameterization) : ShellTestCase()
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(
@@ -6562,7 +7339,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)
@@ -6576,6 +7359,72 @@ 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_desktopNotShowing_updatesDesktopEnterExitListener() {
+ setUpFreeformTask(displayId = DEFAULT_DISPLAY, deskId = 0)
+ taskRepository.setDeskInactive(deskId = 0)
+
+ controller.startLaunchTransition(
+ transitionType = TRANSIT_OPEN,
+ wct = WindowContainerTransaction(),
+ launchingTaskId = null,
+ deskId = 0,
+ displayId = DEFAULT_DISPLAY,
+ )
+
+ verify(desktopModeEnterExitTransitionListener).onEnterDesktopModeTransitionStarted(any())
+ }
+
+ @Test
+ @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()
@@ -6590,8 +7439,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() })
@@ -6760,8 +7615,7 @@ class DesktopTasksControllerTest(flags: FlagsParameterization) : ShellTestCase()
}
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 {
+ setUpFreeformTask().apply {
pictureInPictureParams =
PictureInPictureParams.Builder().setAutoEnterEnabled(autoEnterEnabled).build()
}
@@ -6902,7 +7756,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
}
@@ -6940,7 +7794,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..a7dc706eb6c9 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
@@ -48,13 +48,11 @@ 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 org.junit.Before
@@ -95,7 +93,6 @@ class DesktopTasksTransitionObserverTest {
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
@@ -119,7 +116,6 @@ class DesktopTasksTransitionObserverTest {
mixedHandler,
backAnimationController,
desktopWallpaperActivityTokenProvider,
- desksTransitionObserver,
shellInit,
)
}
@@ -340,51 +336,6 @@ 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()
-
- transitionObserver.onTransitionReady(
- transition = mock(),
- info = createToFrontTransition(wallpaperTask),
- startTransaction = mock(),
- finishTransaction = mock(),
- )
-
- verify(desktopWallpaperActivityTokenProvider)
- .setWallpaperActivityIsVisible(isVisible = true, wallpaperTask.displayId)
- }
-
- @Test
- fun transitToBackWallpaper_wallpaperActivityVisibilitySaved() {
- val wallpaperTask = createWallpaperTaskInfo()
-
- transitionObserver.onTransitionReady(
- transition = mock(),
- info = createToBackTransition(wallpaperTask),
- startTransaction = mock(),
- finishTransaction = mock(),
- )
-
- verify(desktopWallpaperActivityTokenProvider)
- .setWallpaperActivityIsVisible(isVisible = false, wallpaperTask.displayId)
- }
-
- @Test
fun transitCloseWallpaper_wallpaperActivityVisibilitySaved() {
val wallpaperTask = createWallpaperTaskInfo()
@@ -407,7 +358,7 @@ class DesktopTasksTransitionObserverTest {
transitionObserver.onTransitionReady(
transition = pipTransition,
- info = createOpenChangeTransition(task, TRANSIT_PIP),
+ info = createOpenChangeTransition(task, type = TRANSIT_PIP),
startTransaction = mock(),
finishTransaction = mock(),
)
@@ -448,21 +399,6 @@ class DesktopTasksTransitionObserverTest {
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..c40a04c47b9b 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
@@ -41,6 +41,7 @@ object DesktopTestHelpers {
.setActivityType(ACTIVITY_TYPE_STANDARD)
.setWindowingMode(WINDOWING_MODE_FREEFORM)
.setLastActiveTime(100)
+ .setUserId(DEFAULT_USER_ID)
.apply { bounds?.let { setBounds(it) } }
.build()
@@ -50,6 +51,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 +65,7 @@ object DesktopTestHelpers {
.setToken(MockToken().token())
.setActivityType(ACTIVITY_TYPE_STANDARD)
.setWindowingMode(WINDOWING_MODE_MULTI_WINDOW)
+ .setUserId(DEFAULT_USER_ID)
.setLastActiveTime(100)
.build()
@@ -72,6 +75,7 @@ object DesktopTestHelpers {
.setToken(MockToken().token())
.setActivityType(ACTIVITY_TYPE_HOME)
.setWindowingMode(WINDOWING_MODE_FULLSCREEN)
+ .setUserId(DEFAULT_USER_ID)
.setLastActiveTime(100)
.build()
@@ -91,4 +95,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..6e7adf368155 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
@@ -53,6 +58,7 @@ import org.mockito.ArgumentMatchers.anyInt
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
@@ -78,6 +84,7 @@ class DragToDesktopTransitionHandlerTest : ShellTestCase() {
@Mock private lateinit var homeTaskLeash: SurfaceControl
@Mock private lateinit var desktopUserRepositories: DesktopUserRepositories
@Mock private lateinit var bubbleController: BubbleController
+ @Mock private lateinit var visualIndicator: DesktopModeVisualIndicator
private val transactionSupplier = Supplier { mock<SurfaceControl.Transaction>() }
@@ -114,6 +121,14 @@ class DragToDesktopTransitionHandlerTest : ShellTestCase() {
.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
@@ -675,17 +690,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 +706,123 @@ class DragToDesktopTransitionHandlerTest : ShellTestCase() {
}
@Test
+ @EnableFlags(FLAG_ENABLE_DRAG_TO_DESKTOP_INCOMING_TRANSITIONS_BUGFIX)
+ fun mergeOtherTransition_cancelAndEndNotYetRequested_doesntInterruptsStartDrag() {
+ 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_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()
+ }
+
+ 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 +866,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 +919,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 +942,7 @@ class DragToDesktopTransitionHandlerTest : ShellTestCase() {
)
)
.thenReturn(token)
- handler.startDragToDesktopTransition(task, dragAnimator)
+ handler.startDragToDesktopTransition(task, dragAnimator, visualIndicator)
return token
}
@@ -845,6 +1009,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 +1026,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..c7518d5914b4 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,6 +50,8 @@ 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
@@ -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/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/pip/tv/OWNERS b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/tv/OWNERS
index 736d4cff6ce8..a7d1890a0286 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/tv/OWNERS
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/tv/OWNERS
@@ -1,3 +1,2 @@
# WM shell sub-module TV pip owners
-galinap@google.com
-bronger@google.com \ No newline at end of file
+bronger@google.com
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 607e6a450883..14f9ffc52a66 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
@@ -22,6 +22,7 @@ import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.atLeastOnce;
import static org.mockito.Mockito.clearInvocations;
+import static org.mockito.Mockito.never;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.verifyZeroInteractions;
import static org.mockito.Mockito.when;
@@ -126,7 +127,7 @@ public class PipAlphaAnimatorTest {
}
@Test
- public void onAnimationStart_setCornerAndShadowRadii() {
+ public void onAnimationStart_fadeInAnimator_setCornerAndShadowRadii() {
mPipAlphaAnimator = new PipAlphaAnimator(mMockContext, mTestLeash, mMockStartTransaction,
mMockFinishTransaction, PipAlphaAnimator.FADE_IN);
mPipAlphaAnimator.setSurfaceControlTransactionFactory(mMockFactory);
@@ -143,7 +144,26 @@ public class PipAlphaAnimatorTest {
}
@Test
- public void onAnimationUpdate_setCornerAndShadowRadii() {
+ public void onAnimationStart_fadeOutAnimator_setCornerNoShadowRadii() {
+ mPipAlphaAnimator = new PipAlphaAnimator(mMockContext, mTestLeash, mMockStartTransaction,
+ mMockFinishTransaction, PipAlphaAnimator.FADE_OUT);
+ mPipAlphaAnimator.setSurfaceControlTransactionFactory(mMockFactory);
+
+ InstrumentationRegistry.getInstrumentation().runOnMainSync(() -> {
+ mPipAlphaAnimator.start();
+ mPipAlphaAnimator.pause();
+ });
+
+ verify(mMockStartTransaction, atLeastOnce())
+ .setCornerRadius(eq(mTestLeash), eq(TEST_CORNER_RADIUS));
+ verify(mMockStartTransaction, never())
+ .setShadowRadius(eq(mTestLeash), eq(TEST_SHADOW_RADIUS));
+ verify(mMockStartTransaction, atLeastOnce())
+ .setShadowRadius(eq(mTestLeash), eq(0f));
+ }
+
+ @Test
+ public void onAnimationUpdate_fadeInAnimator_setCornerAndShadowRadii() {
mPipAlphaAnimator = new PipAlphaAnimator(mMockContext, mTestLeash, mMockStartTransaction,
mMockFinishTransaction, PipAlphaAnimator.FADE_IN);
mPipAlphaAnimator.setSurfaceControlTransactionFactory(mMockFactory);
@@ -160,7 +180,26 @@ public class PipAlphaAnimatorTest {
}
@Test
- public void onAnimationEnd_setCornerAndShadowRadii() {
+ public void onAnimationUpdate_fadeOutAnimator_setCornerNoShadowRadii() {
+ mPipAlphaAnimator = new PipAlphaAnimator(mMockContext, mTestLeash, mMockStartTransaction,
+ mMockFinishTransaction, PipAlphaAnimator.FADE_OUT);
+ mPipAlphaAnimator.setSurfaceControlTransactionFactory(mMockFactory);
+
+ InstrumentationRegistry.getInstrumentation().runOnMainSync(() -> {
+ mPipAlphaAnimator.start();
+ mPipAlphaAnimator.pause();
+ });
+
+ verify(mMockAnimateTransaction, atLeastOnce())
+ .setCornerRadius(eq(mTestLeash), eq(TEST_CORNER_RADIUS));
+ verify(mMockAnimateTransaction, never())
+ .setShadowRadius(eq(mTestLeash), eq(TEST_SHADOW_RADIUS));
+ verify(mMockAnimateTransaction, atLeastOnce())
+ .setShadowRadius(eq(mTestLeash), eq(0f));
+ }
+
+ @Test
+ public void onAnimationEnd_fadeInAnimator_setCornerAndShadowRadii() {
mPipAlphaAnimator = new PipAlphaAnimator(mMockContext, mTestLeash, mMockStartTransaction,
mMockFinishTransaction, PipAlphaAnimator.FADE_IN);
mPipAlphaAnimator.setSurfaceControlTransactionFactory(mMockFactory);
@@ -177,6 +216,25 @@ public class PipAlphaAnimatorTest {
}
@Test
+ public void onAnimationEnd_fadeOutAnimator_setCornerNoShadowRadii() {
+ mPipAlphaAnimator = new PipAlphaAnimator(mMockContext, mTestLeash, mMockStartTransaction,
+ mMockFinishTransaction, PipAlphaAnimator.FADE_OUT);
+ mPipAlphaAnimator.setSurfaceControlTransactionFactory(mMockFactory);
+
+ InstrumentationRegistry.getInstrumentation().runOnMainSync(() -> {
+ mPipAlphaAnimator.start();
+ mPipAlphaAnimator.end();
+ });
+
+ verify(mMockFinishTransaction, atLeastOnce())
+ .setCornerRadius(eq(mTestLeash), eq(TEST_CORNER_RADIUS));
+ verify(mMockFinishTransaction, never())
+ .setShadowRadius(eq(mTestLeash), eq(TEST_SHADOW_RADIUS));
+ verify(mMockFinishTransaction, atLeastOnce())
+ .setShadowRadius(eq(mTestLeash), eq(0f));
+ }
+
+ @Test
public void onAnimationEnd_fadeInAnimator_leashVisibleAtEnd() {
mPipAlphaAnimator = new PipAlphaAnimator(mMockContext, mTestLeash, mMockStartTransaction,
mMockFinishTransaction, PipAlphaAnimator.FADE_IN);
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..5029371c3419 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,6 +24,7 @@ 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;
@@ -35,7 +36,9 @@ 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,
@@ -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/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 180a6915b45f..95498cbbe53c 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
@@ -18,7 +18,6 @@ package com.android.wm.shell.shared.bubbles
import android.content.Context
import android.graphics.Rect
-import android.view.View
import android.widget.FrameLayout
import androidx.core.animation.AnimatorTestRule
import androidx.test.core.app.ApplicationProvider.getApplicationContext
@@ -58,15 +57,15 @@ class DropTargetManagerTest {
private val bubbleRightDragZone =
DragZone.Bubble.Right(bounds = Rect(200, 0, 300, 100), dropTarget = Rect(200, 0, 280, 150))
- private val dropTargetView: View
- get() = container.getChildAt(0)
+ private val dropTargetView: DropTargetView
+ get() = container.getChildAt(0) as DropTargetView
@Before
fun setUp() {
container = FrameLayout(context)
dragZoneChangedListener = FakeDragZoneChangedListener()
dropTargetManager =
- DropTargetManager(context, container, isLayoutRtl = false, dragZoneChangedListener)
+ DropTargetManager(context, container, dragZoneChangedListener)
}
@Test
@@ -229,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),
@@ -238,8 +253,9 @@ class DropTargetManagerTest {
InstrumentationRegistry.getInstrumentation().runOnMainSync {
dropTargetManager.onDragEnded()
- // advance the timer by 100ms so the animation doesn't complete
- animatorTestRule.advanceTimeBy(100)
+ // advance the timer by 50ms so the animation doesn't complete
+ // needs to be < DropTargetManager.DROP_TARGET_ALPHA_OUT_DURATION
+ animatorTestRule.advanceTimeBy(50)
}
assertThat(container.childCount).isEqualTo(1)
@@ -320,16 +336,17 @@ class DropTargetManagerTest {
}
private fun verifyDropTargetPosition(rect: Rect) {
- assertThat(dropTargetView.scaleX).isEqualTo(rect.width())
- assertThat(dropTargetView.scaleY).isEqualTo(rect.height())
- assertThat(dropTargetView.translationX).isEqualTo(rect.exactCenterX())
- assertThat(dropTargetView.translationY).isEqualTo(rect.exactCenterY())
+ assertThat(dropTargetView.getRect().left).isEqualTo(rect.left)
+ assertThat(dropTargetView.getRect().top).isEqualTo(rect.top)
+ assertThat(dropTargetView.getRect().right).isEqualTo(rect.right)
+ assertThat(dropTargetView.getRect().bottom).isEqualTo(rect.bottom)
}
private class FakeDragZoneChangedListener : DropTargetManager.DragZoneChangedListener {
var initialDragZone: DragZone? = null
var fromDragZone: DragZone? = null
var toDragZone: DragZone? = null
+ var endedDragZone: DragZone? = null
override fun onInitialDragZoneSet(dragZone: DragZone) {
initialDragZone = dragZone
@@ -339,5 +356,9 @@ class DropTargetManagerTest {
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/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..e246329446dc 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
@@ -475,7 +475,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/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/DesktopModeWindowDecorViewModelTests.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModelTests.kt
index e89a122595d5..d69509faf4ec 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
@@ -115,7 +115,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()) }
@@ -394,7 +395,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)
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..a1f40fdefee9 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,6 +79,7 @@ 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
@@ -174,6 +175,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 +187,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,
@@ -222,6 +228,7 @@ open class DesktopModeWindowDecorViewModelTestsBase : ShellTestCase() {
Optional.of(mockTasksLimiter),
mockAppHandleEducationController,
mockAppToWebEducationController,
+ appHandleAndHeaderVisibilityHelper,
mockCaptionHandleRepository,
Optional.of(mockActivityOrientationChangeHandler),
mockTaskPositionerFactory,
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..1371b38a579f 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
@@ -167,6 +167,7 @@ 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;
@@ -415,6 +416,7 @@ 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,
@@ -616,6 +618,7 @@ 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,
@@ -650,6 +653,7 @@ 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,
@@ -728,6 +732,7 @@ 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,
@@ -755,6 +760,7 @@ 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,
@@ -781,6 +787,7 @@ 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,
@@ -807,6 +814,7 @@ 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,
@@ -832,6 +840,7 @@ 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,
@@ -857,6 +866,7 @@ 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,
@@ -883,6 +893,7 @@ 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,
@@ -901,6 +912,7 @@ 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,
@@ -911,6 +923,33 @@ public class DesktopModeWindowDecorationTests extends ShellTestCase {
}
@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);
+
+ 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,6 +966,7 @@ public class DesktopModeWindowDecorationTests extends ShellTestCase {
DEFAULT_IS_STATUSBAR_VISIBLE,
/* isKeyguardVisibleAndOccluded */ true,
/* inFullImmersiveMode */ true,
+ DEFAULT_IS_DRAGGING,
new InsetsState(),
DEFAULT_HAS_GLOBAL_FOCUS,
mExclusionRegion,
@@ -1588,6 +1628,7 @@ 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,
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..9a2e2fad50be 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)
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..c8ccac35d4c4 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
@@ -116,8 +118,10 @@ 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)
@@ -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)
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..bc8faedd77a9 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
@@ -114,6 +114,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 +135,7 @@ class DesktopTilingWindowDecorationTest : ShellTestCase() {
userRepositories,
desktopModeEventLogger,
focusTransitionObserver,
- mainExecutor
+ mainExecutor,
)
whenever(context.createContextAsUser(any(), any())).thenReturn(context)
whenever(userRepositories.current).thenReturn(desktopRepository)
@@ -158,7 +159,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 +187,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 +223,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 +311,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 +348,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 +382,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 +497,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
diff --git a/libs/androidfw/include/androidfw/ResourceTypes.h b/libs/androidfw/include/androidfw/ResourceTypes.h
index 30594dcfa939..0d45149267cf 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.
*
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/CanvasTransform.cpp b/libs/hwui/CanvasTransform.cpp
index 30e7a628f1f6..6f60d01e4395 100644
--- a/libs/hwui/CanvasTransform.cpp
+++ b/libs/hwui/CanvasTransform.cpp
@@ -55,12 +55,20 @@ SkColor makeDark(SkColor color) {
}
}
+SkColor invert(SkColor color) {
+ Lab lab = sRGBToLab(color);
+ lab.L = 100 - lab.L;
+ return LabToSRGB(lab, SkColorGetA(color));
+}
+
SkColor transformColor(ColorTransform transform, SkColor color) {
switch (transform) {
case ColorTransform::Light:
return makeLight(color);
case ColorTransform::Dark:
return makeDark(color);
+ case ColorTransform::Invert:
+ return invert(color);
default:
return color;
}
@@ -80,19 +88,6 @@ SkColor transformColorInverse(ColorTransform transform, SkColor color) {
static void applyColorTransform(ColorTransform transform, SkPaint& paint) {
if (transform == ColorTransform::None) return;
- if (transform == ColorTransform::Invert) {
- auto filter = SkHighContrastFilter::Make(
- {/* grayscale= */ false, SkHighContrastConfig::InvertStyle::kInvertLightness,
- /* contrast= */ 0.0f});
-
- if (paint.getColorFilter()) {
- paint.setColorFilter(SkColorFilters::Compose(filter, paint.refColorFilter()));
- } else {
- paint.setColorFilter(filter);
- }
- return;
- }
-
SkColor newColor = transformColor(transform, paint.getColor());
paint.setColor(newColor);
@@ -112,6 +107,22 @@ static void applyColorTransform(ColorTransform transform, SkPaint& paint) {
paint.setShader(SkGradientShader::MakeLinear(
info.fPoints, info.fColors, info.fColorOffsets, info.fColorCount,
info.fTileMode, info.fGradientFlags, nullptr));
+ } else {
+ if (transform == ColorTransform::Invert) {
+ // Since we're trying to invert every thing around this draw call, we invert
+ // the color of the draw call if we don't know what it is.
+ auto filter = SkHighContrastFilter::Make(
+ {/* grayscale= */ false,
+ SkHighContrastConfig::InvertStyle::kInvertLightness,
+ /* contrast= */ 0.0f});
+
+ if (paint.getColorFilter()) {
+ paint.setColorFilter(SkColorFilters::Compose(filter, paint.refColorFilter()));
+ } else {
+ paint.setColorFilter(filter);
+ }
+ return;
+ }
}
}
@@ -150,8 +161,13 @@ bool transformPaint(ColorTransform transform, SkPaint* paint) {
}
bool transformPaint(ColorTransform transform, SkPaint* paint, BitmapPalette palette) {
- palette = filterPalette(paint, palette);
bool shouldInvert = false;
+ if (transform == ColorTransform::Invert && palette != BitmapPalette::Colorful) {
+ // When the transform is Invert we invert any image that is not deemed "colorful",
+ // regardless of calculated image brightness.
+ shouldInvert = true;
+ }
+ palette = filterPalette(paint, palette);
if (palette == BitmapPalette::Light && transform == ColorTransform::Dark) {
shouldInvert = true;
}
diff --git a/libs/hwui/OWNERS b/libs/hwui/OWNERS
index 70d13ab8b3e5..9c06fd5f0225 100644
--- a/libs/hwui/OWNERS
+++ b/libs/hwui/OWNERS
@@ -3,7 +3,7 @@
alecmouri@google.com
djsollen@google.com
jreck@google.com
-njawad@google.com
+nscobie@google.com
sumir@google.com
# For text, e.g. Typeface, Font, Minikin, etc.
diff --git a/libs/hwui/RenderNode.cpp b/libs/hwui/RenderNode.cpp
index 4801bd1038a3..8b4e59aa73e2 100644
--- a/libs/hwui/RenderNode.cpp
+++ b/libs/hwui/RenderNode.cpp
@@ -27,6 +27,7 @@
#include "DamageAccumulator.h"
#include "Debug.h"
+#include "FeatureFlags.h"
#include "Properties.h"
#include "TreeInfo.h"
#include "VectorDrawable.h"
@@ -398,26 +399,32 @@ void RenderNode::syncDisplayList(TreeObserver& observer, TreeInfo* info) {
deleteDisplayList(observer, info);
mDisplayList = std::move(mStagingDisplayList);
if (mDisplayList) {
- WebViewSyncData syncData{.applyForceDark = shouldEnableForceDark(info)};
+ WebViewSyncData syncData{.applyForceDark = shouldEnableForceDark(info) ||
+ (info && isForceInvertDark(*info))};
mDisplayList.syncContents(syncData);
handleForceDark(info);
}
}
+// Return true if the tree should use the force invert feature that inverts
+// the entire tree to darken it.
inline bool RenderNode::isForceInvertDark(TreeInfo& info) {
- return CC_UNLIKELY(
- info.forceDarkType == android::uirenderer::ForceDarkType::FORCE_INVERT_COLOR_DARK);
+ return CC_UNLIKELY(info.forceDarkType ==
+ android::uirenderer::ForceDarkType::FORCE_INVERT_COLOR_DARK);
}
+// Return true if the tree should use the force dark feature that selectively
+// darkens light nodes on the tree.
inline bool RenderNode::shouldEnableForceDark(TreeInfo* info) {
- return CC_UNLIKELY(
- info &&
- (!info->disableForceDark || isForceInvertDark(*info)));
+ return CC_UNLIKELY(info && !info->disableForceDark);
}
-
-
-void RenderNode::handleForceDark(android::uirenderer::TreeInfo *info) {
+void RenderNode::handleForceDark(TreeInfo *info) {
+ if (CC_UNLIKELY(view_accessibility_flags::force_invert_color() && info &&
+ isForceInvertDark(*info))) {
+ mDisplayList.applyColorTransform(ColorTransform::Invert);
+ return;
+ }
if (!shouldEnableForceDark(info)) {
return;
}
@@ -427,13 +434,7 @@ void RenderNode::handleForceDark(android::uirenderer::TreeInfo *info) {
children.push_back(node);
});
if (mDisplayList.hasText()) {
- if (isForceInvertDark(*info) && mDisplayList.hasFill()) {
- // Handle a special case for custom views that draw both text and background in the
- // same RenderNode, which would otherwise be altered to white-on-white text.
- usage = UsageHint::Container;
- } else {
- usage = UsageHint::Foreground;
- }
+ usage = UsageHint::Foreground;
}
if (usage == UsageHint::Unknown) {
if (children.size() > 1) {
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/hwui/Bitmap.cpp b/libs/hwui/hwui/Bitmap.cpp
index 63a024b8e780..3ef970830dc4 100644
--- a/libs/hwui/hwui/Bitmap.cpp
+++ b/libs/hwui/hwui/Bitmap.cpp
@@ -16,6 +16,8 @@
#include "Bitmap.h"
#include <android-base/file.h>
+
+#include "FeatureFlags.h"
#include "HardwareBitmapUploader.h"
#include "Properties.h"
#ifdef __ANDROID__ // Layoutlib does not support render thread
@@ -547,9 +549,16 @@ BitmapPalette Bitmap::computePalette(const SkImageInfo& info, const void* addr,
}
ALOGV("samples = %d, hue [min = %f, max = %f, avg = %f]; saturation [min = %f, max = %f, avg = "
- "%f]",
+ "%f] %d x %d",
sampledCount, hue.min(), hue.max(), hue.average(), saturation.min(), saturation.max(),
- saturation.average());
+ saturation.average(), info.width(), info.height());
+
+ if (CC_UNLIKELY(view_accessibility_flags::force_invert_color())) {
+ if (saturation.delta() > 0.1f ||
+ (hue.delta() > 20 && saturation.average() > 0.2f && value.average() < 0.9f)) {
+ return BitmapPalette::Colorful;
+ }
+ }
if (hue.delta() <= 20 && saturation.delta() <= .1f) {
if (value.average() >= .5f) {
diff --git a/libs/hwui/hwui/Bitmap.h b/libs/hwui/hwui/Bitmap.h
index 4e9bcf27c0ef..0fe5fe88f715 100644
--- a/libs/hwui/hwui/Bitmap.h
+++ b/libs/hwui/hwui/Bitmap.h
@@ -49,6 +49,7 @@ enum class BitmapPalette {
Unknown,
Light,
Dark,
+ Colorful,
};
namespace uirenderer {
diff --git a/libs/hwui/jni/GIFMovie.cpp b/libs/hwui/jni/GIFMovie.cpp
index 6c82aa1ca27d..476b6fda5007 100644
--- a/libs/hwui/jni/GIFMovie.cpp
+++ b/libs/hwui/jni/GIFMovie.cpp
@@ -63,7 +63,7 @@ GIFMovie::GIFMovie(SkStream* stream)
}
fCurrIndex = -1;
fLastDrawIndex = -1;
- fPaintingColor = SkPackARGB32(0, 0, 0, 0);
+ fPaintingColor = SK_AlphaTRANSPARENT;
}
GIFMovie::~GIFMovie()
@@ -127,7 +127,7 @@ static void copyLine(uint32_t* dst, const unsigned char* src, const ColorMapObje
for (; width > 0; width--, src++, dst++) {
if (*src != transparent && *src < cmap->ColorCount) {
const GifColorType& col = cmap->Colors[*src];
- *dst = SkPackARGB32(0xFF, col.Red, col.Green, col.Blue);
+ *dst = SkColorSetRGB(col.Red, col.Green, col.Blue);
}
}
}
@@ -395,10 +395,10 @@ bool GIFMovie::onGetBitmap(SkBitmap* bm)
lastIndex = fGIF->ImageCount - 1;
}
- SkColor bgColor = SkPackARGB32(0, 0, 0, 0);
+ SkColor bgColor = SK_ColorTRANSPARENT;
if (gif->SColorMap != nullptr && gif->SBackGroundColor < gif->SColorMap->ColorCount) {
const GifColorType& col = gif->SColorMap->Colors[gif->SBackGroundColor];
- bgColor = SkColorSetARGB(0xFF, col.Red, col.Green, col.Blue);
+ bgColor = SkColorSetRGB(col.Red, col.Green, col.Blue);
}
// draw each frames - not intelligent way
@@ -411,7 +411,7 @@ bool GIFMovie::onGetBitmap(SkBitmap* bm)
if (!trans && gif->SColorMap != nullptr) {
fPaintingColor = bgColor;
} else {
- fPaintingColor = SkColorSetARGB(0, 0, 0, 0);
+ fPaintingColor = SK_ColorTRANSPARENT;
}
bm->eraseColor(fPaintingColor);
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/LocationRequest.java b/location/java/android/location/LocationRequest.java
index 80b55e2c1244..5a993bfcc9cf 100644
--- a/location/java/android/location/LocationRequest.java
+++ b/location/java/android/location/LocationRequest.java
@@ -33,6 +33,7 @@ import android.annotation.SystemApi;
import android.compat.annotation.ChangeId;
import android.compat.annotation.EnabledAfter;
import android.content.pm.PackageManager;
+import android.location.flags.Flags;
import android.os.Build;
import android.os.Parcel;
import android.os.Parcelable;
@@ -181,7 +182,8 @@ public final class LocationRequest implements Parcelable {
public static final int POWER_HIGH = 203;
private static final long IMPLICIT_MIN_UPDATE_INTERVAL = -1;
- private static final double IMPLICIT_MIN_UPDATE_INTERVAL_FACTOR = 1D / 6D;
+ private static final double LEGACY_IMPLICIT_MIN_UPDATE_INTERVAL_FACTOR = 1D / 6D;
+ private static final double IMPLICIT_MIN_UPDATE_INTERVAL_FACTOR = 1D / 2D;
private @Nullable String mProvider;
private @Quality int mQuality;
@@ -553,7 +555,10 @@ public final class LocationRequest implements Parcelable {
*/
public @IntRange(from = 0) long getMinUpdateIntervalMillis() {
if (mMinUpdateIntervalMillis == IMPLICIT_MIN_UPDATE_INTERVAL) {
- return (long) (mIntervalMillis * IMPLICIT_MIN_UPDATE_INTERVAL_FACTOR);
+ if (Flags.updateMinLocationRequestInterval()) {
+ return (long) (mIntervalMillis * IMPLICIT_MIN_UPDATE_INTERVAL_FACTOR);
+ }
+ return (long) (mIntervalMillis * LEGACY_IMPLICIT_MIN_UPDATE_INTERVAL_FACTOR);
} else {
// the min is only necessary in case someone use a deprecated function to mess with the
// interval or min update interval
diff --git a/location/java/android/location/flags/location.aconfig b/location/java/android/location/flags/location.aconfig
index 1b38982f48c1..f8eb41826c6f 100644
--- a/location/java/android/location/flags/location.aconfig
+++ b/location/java/android/location/flags/location.aconfig
@@ -168,3 +168,21 @@ flag {
description: "Flag for GNSS assistance interface"
bug: "209078566"
}
+
+flag {
+ name: "update_min_location_request_interval"
+ namespace: "location"
+ description: "Flag for updating the default logic for the minimal interval for location request"
+ bug: "397444378"
+ metadata {
+ purpose: PURPOSE_BUGFIX
+ }
+}
+
+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
+}
diff --git a/location/java/com/android/internal/location/GpsNetInitiatedHandler.java b/location/java/com/android/internal/location/GpsNetInitiatedHandler.java
index fb89973bcc11..66de8c791fdd 100644
--- a/location/java/com/android/internal/location/GpsNetInitiatedHandler.java
+++ b/location/java/com/android/internal/location/GpsNetInitiatedHandler.java
@@ -27,8 +27,6 @@ import android.telephony.TelephonyManager;
import android.telephony.emergency.EmergencyNumber;
import android.util.Log;
-import com.android.internal.annotations.KeepForWeakReference;
-
import java.util.concurrent.TimeUnit;
/**
@@ -94,7 +92,6 @@ public class GpsNetInitiatedHandler {
// The internal implementation of TelephonyManager uses WeakReference so we have to keep a
// reference here.
- @KeepForWeakReference
private final EmergencyCallListener mEmergencyCallListener = new EmergencyCallListener();
private final EmergencyCallCallback mEmergencyCallCallback;
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 9bb31d0076c9..f3b21bfdaa3c 100644
--- a/media/java/android/media/MediaCodecInfo.java
+++ b/media/java/android/media/MediaCodecInfo.java
@@ -1223,8 +1223,14 @@ public final class MediaCodecInfo {
private static final String TAG = "CodecCapabilities";
// NEW-STYLE CAPABILITIES
+ // Capabilities for an audio codec.
+ @Nullable
private AudioCapabilities mAudioCaps;
+ // Capabilities for a video codec.
+ @Nullable
private VideoCapabilities mVideoCaps;
+ // Capabilities specific to an encoder (vs. a decoder).
+ @Nullable
private EncoderCapabilities mEncoderCaps;
private MediaFormat mDefaultFormat;
@@ -1262,6 +1268,7 @@ public final class MediaCodecInfo {
/**
* Returns the audio capabilities or {@code null} if this is not an audio codec.
*/
+ @Nullable
public AudioCapabilities getAudioCapabilities() {
return mAudioCaps;
}
@@ -1273,6 +1280,7 @@ public final class MediaCodecInfo {
/**
* Returns the encoding capabilities or {@code null} if this is not an encoder.
*/
+ @Nullable
public EncoderCapabilities getEncoderCapabilities() {
return mEncoderCaps;
}
@@ -1284,6 +1292,7 @@ public final class MediaCodecInfo {
/**
* Returns the video capabilities or {@code null} if this is not a video codec.
*/
+ @Nullable
public VideoCapabilities getVideoCapabilities() {
return mVideoCaps;
}
@@ -3827,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/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/Utils.java b/media/java/android/media/Utils.java
index d6e27b0ffa75..9db527bfaa2d 100644
--- a/media/java/android/media/Utils.java
+++ b/media/java/android/media/Utils.java
@@ -18,6 +18,8 @@ package android.media;
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.annotation.SuppressLint;
+import android.annotation.TestApi;
import android.content.ContentResolver;
import android.content.Context;
import android.content.res.Resources;
@@ -61,16 +63,30 @@ import java.util.concurrent.Executor;
*
* @hide
*/
+@TestApi
+@SuppressLint({"UnflaggedApi", "StaticUtils"}) // Test API
public class Utils {
private static final String TAG = "Utils";
+ /** @hide
+ * The vibration uri key parameter
+ */
+ @TestApi
+ @SuppressLint("UnflaggedApi") // Test API
public static final String VIBRATION_URI_PARAM = "vibration_uri";
+ /** @hide
+ * Indicates the synchronized vibration
+ */
+ @TestApi
+ @SuppressLint("UnflaggedApi") // Test API
public static final String SYNCHRONIZED_VIBRATION = "synchronized";
/**
* Sorts distinct (non-intersecting) range array in ascending order.
* @throws java.lang.IllegalArgumentException if ranges are not distinct
+ *
+ * @hide
*/
public static <T extends Comparable<? super T>> void sortDistinctRanges(Range<T>[] ranges) {
Arrays.sort(ranges, new Comparator<Range<T>>() {
@@ -92,6 +108,8 @@ public class Utils {
* @param one a sorted set of non-intersecting ranges in ascending order
* @param another another sorted set of non-intersecting ranges in ascending order
* @return the intersection of the two sets, sorted in ascending order
+ *
+ * @hide
*/
public static <T extends Comparable<? super T>>
Range<T>[] intersectSortedDistinctRanges(Range<T>[] one, Range<T>[] another) {
@@ -124,6 +142,8 @@ public class Utils {
* @return if the value is in one of the ranges, it returns the index of that range. Otherwise,
* the return value is {@code (-1-index)} for the {@code index} of the range that is
* immediately following {@code value}.
+ *
+ * @hide
*/
public static <T extends Comparable<? super T>>
int binarySearchDistinctRanges(Range<T>[] ranges, T value) {
@@ -358,6 +378,8 @@ public class Utils {
* @param fileName desired name for the file.
* @param mimeType MIME type of the file to create.
* @return the File object in the storage, or null if an error occurs.
+ *
+ * @hide
*/
public static File getUniqueExternalFile(Context context, String subdirectory, String fileName,
String mimeType) {
@@ -676,6 +698,8 @@ public class Utils {
* Must match the implementation of BluetoothUtils.toAnonymizedAddress()
* @param address MAC address to be anonymized
* @return anonymized MAC address
+ *
+ * @hide
*/
public static @Nullable String anonymizeBluetoothAddress(@Nullable String address) {
if (address == null) {
@@ -693,6 +717,8 @@ public class Utils {
* @param deviceType the internal type of the audio device
* @param address MAC address to be anonymized
* @return anonymized MAC address
+ *
+ * @hide
*/
public static @Nullable String anonymizeBluetoothAddress(
int deviceType, @Nullable String address) {
@@ -707,6 +733,8 @@ public class Utils {
*
* @param context the {@link Context}
* @return {@code true} if the device supports ringtone vibration
+ *
+ * @hide
*/
public static boolean isRingtoneVibrationSettingsSupported(Context context) {
final Resources res = context.getResources();
@@ -719,6 +747,8 @@ public class Utils {
*
* @param ringtoneUri the ringtone Uri
* @return {@code true} if the Uri has vibration parameter
+ *
+ * @hide
*/
public static boolean hasVibration(Uri ringtoneUri) {
if (ringtoneUri == null) {
@@ -734,6 +764,8 @@ public class Utils {
* @param ringtoneUri the ringtone Uri
* @return parsed {@link Uri} of vibration parameter, {@code null} if the vibration parameter
* is not found.
+ *
+ * @hide
*/
public static @Nullable Uri getVibrationUri(Uri ringtoneUri) {
if (ringtoneUri == null) {
@@ -751,6 +783,8 @@ public class Utils {
*
* @param vibrator the vibrator to resolve the vibration file
* @param vibrationUri the vibration file Uri to represent a vibration
+ *
+ * @hide
*/
@SuppressWarnings("FlaggedApi") // VibrationXmlParser is available internally as hidden APIs.
public static VibrationEffect parseVibrationEffect(Vibrator vibrator, Uri vibrationUri) {
diff --git a/media/java/android/media/flags/media_better_together.aconfig b/media/java/android/media/flags/media_better_together.aconfig
index 0deed3982d9b..e39a0aa8717e 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."
diff --git a/media/java/android/media/quality/Android.bp b/media/java/android/media/quality/Android.bp
new file mode 100644
index 000000000000..080d5266ccb7
--- /dev/null
+++ b/media/java/android/media/quality/Android.bp
@@ -0,0 +1,39 @@
+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",
+}
+
+aidl_interface {
+ name: "media_quality_aidl_interface",
+ unstable: true,
+ local_include_dir: "aidl",
+ backend: {
+ java: {
+ enabled: true,
+ },
+ cpp: {
+ enabled: false,
+ },
+ ndk: {
+ enabled: false,
+ },
+ rust: {
+ enabled: false,
+ },
+ },
+ srcs: [
+ ":framework-media-quality-sources-aidl",
+ ],
+}
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..2851306f6e4d 100644
--- a/media/java/android/media/quality/ActiveProcessingPicture.aidl
+++ b/media/java/android/media/quality/aidl/android/media/quality/ActiveProcessingPicture.aidl
diff --git a/media/java/android/media/quality/AmbientBacklightEvent.aidl b/media/java/android/media/quality/aidl/android/media/quality/AmbientBacklightEvent.aidl
index 174cd461e846..174cd461e846 100644
--- a/media/java/android/media/quality/AmbientBacklightEvent.aidl
+++ b/media/java/android/media/quality/aidl/android/media/quality/AmbientBacklightEvent.aidl
diff --git a/media/java/android/media/quality/AmbientBacklightMetadata.aidl b/media/java/android/media/quality/aidl/android/media/quality/AmbientBacklightMetadata.aidl
index b95a474fbf90..b95a474fbf90 100644
--- a/media/java/android/media/quality/AmbientBacklightMetadata.aidl
+++ b/media/java/android/media/quality/aidl/android/media/quality/AmbientBacklightMetadata.aidl
diff --git a/media/java/android/media/quality/AmbientBacklightSettings.aidl b/media/java/android/media/quality/aidl/android/media/quality/AmbientBacklightSettings.aidl
index e2cdd03194cd..e2cdd03194cd 100644
--- a/media/java/android/media/quality/AmbientBacklightSettings.aidl
+++ b/media/java/android/media/quality/aidl/android/media/quality/AmbientBacklightSettings.aidl
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..eb2ac97916f3 100644
--- a/media/java/android/media/quality/ParameterCapability.aidl
+++ b/media/java/android/media/quality/aidl/android/media/quality/ParameterCapability.aidl
diff --git a/media/java/android/media/quality/PictureProfile.aidl b/media/java/android/media/quality/aidl/android/media/quality/PictureProfile.aidl
index 41d018b12f33..41d018b12f33 100644
--- a/media/java/android/media/quality/PictureProfile.aidl
+++ b/media/java/android/media/quality/aidl/android/media/quality/PictureProfile.aidl
diff --git a/media/java/android/media/quality/PictureProfileHandle.aidl b/media/java/android/media/quality/aidl/android/media/quality/PictureProfileHandle.aidl
index 5d14631dbb73..5d14631dbb73 100644
--- a/media/java/android/media/quality/PictureProfileHandle.aidl
+++ b/media/java/android/media/quality/aidl/android/media/quality/PictureProfileHandle.aidl
diff --git a/media/java/android/media/quality/SoundProfile.aidl b/media/java/android/media/quality/aidl/android/media/quality/SoundProfile.aidl
index e79fcaac97be..e79fcaac97be 100644
--- a/media/java/android/media/quality/SoundProfile.aidl
+++ b/media/java/android/media/quality/aidl/android/media/quality/SoundProfile.aidl
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/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/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/native/android/thermal.cpp b/native/android/thermal.cpp
index cefcaf7766bb..93e6ed814c34 100644
--- a/native/android/thermal.cpp
+++ b/native/android/thermal.cpp
@@ -139,7 +139,12 @@ AThermalManager::~AThermalManager() {
mStatusListeners.clear();
if (mServiceStatusListener != nullptr) {
bool success = false;
- mThermalSvc->unregisterThermalStatusListener(mServiceStatusListener, &success);
+ auto ret =
+ mThermalSvc->unregisterThermalStatusListener(mServiceStatusListener, &success);
+ if (!success || !ret.isOk()) {
+ ALOGE("Failed in unregisterThermalStatusListener when AThermalManager is being "
+ "destroyed %d", success);
+ }
mServiceStatusListener = nullptr;
}
}
@@ -148,7 +153,12 @@ AThermalManager::~AThermalManager() {
mHeadroomListeners.clear();
if (mServiceHeadroomListener != nullptr) {
bool success = false;
- mThermalSvc->unregisterThermalHeadroomListener(mServiceHeadroomListener, &success);
+ auto ret = mThermalSvc->unregisterThermalHeadroomListener(mServiceHeadroomListener,
+ &success);
+ if (!success || !ret.isOk()) {
+ ALOGE("Failed in unregisterThermalHeadroomListener when AThermalManager is being "
+ "destroyed %d", success);
+ }
mServiceHeadroomListener = nullptr;
}
}
diff --git a/native/webview/TEST_MAPPING b/native/webview/TEST_MAPPING
index 38580595dc2d..c9b54760491a 100644
--- a/native/webview/TEST_MAPPING
+++ b/native/webview/TEST_MAPPING
@@ -17,15 +17,6 @@
"exclude-annotation": "android.test.FlakyTest"
}
]
- },
- {
- "name": "GtsWebViewHostTestCases",
- "keywords": ["internal"],
- "options": [
- {
- "exclude-annotation": "android.test.FlakyTest"
- }
- ]
}
]
}
diff --git a/packages/CompanionDeviceManager/res/layout/activity_confirmation.xml b/packages/CompanionDeviceManager/res/layout/activity_confirmation.xml
index 0ad7f5f6dfa5..3d011bc00f9f 100644
--- a/packages/CompanionDeviceManager/res/layout/activity_confirmation.xml
+++ b/packages/CompanionDeviceManager/res/layout/activity_confirmation.xml
@@ -76,6 +76,7 @@
android:layout_width="match_parent"
android:layout_height="match_parent"
android:visibility="gone"
+ android:accessibilityLiveRegion="polite"
style="@style/TimeoutMessage" />
<androidx.recyclerview.widget.RecyclerView
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..394efae9da0e 100644
--- a/packages/CompanionDeviceManager/res/values-fr/strings.xml
+++ b/packages/CompanionDeviceManager/res/values-fr/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 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 dd77c6153726..b2c1e604db7e 100644
--- a/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/CompanionAssociationActivity.java
+++ b/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/CompanionAssociationActivity.java
@@ -339,7 +339,7 @@ public class CompanionAssociationActivity extends FragmentActivity implements
private void onDiscoveryStateChanged(DiscoveryState newState) {
switch (newState) {
case IN_PROGRESS: {
- mTimeoutMessage.setText(null);
+ mTimeoutMessage.setVisibility(View.GONE);
mProgressBar.setIndeterminate(true);
break;
}
@@ -351,6 +351,7 @@ public class CompanionAssociationActivity extends FragmentActivity implements
R.string.message_discovery_soft_timeout,
deviceType, discoveryType, profile);
mTimeoutMessage.setText(message);
+ mTimeoutMessage.setVisibility(View.VISIBLE);
break;
}
case FINISHED_STOPPED: {
@@ -363,6 +364,7 @@ public class CompanionAssociationActivity extends FragmentActivity implements
}
}
mTimeoutMessage.setText(getString(R.string.message_discovery_hard_timeout));
+ mTimeoutMessage.setVisibility(View.VISIBLE);
}
mProgressBar.setIndeterminate(false);
break;
@@ -528,6 +530,7 @@ public class CompanionAssociationActivity extends FragmentActivity implements
mVendorHeader.setVisibility(View.VISIBLE);
mProfileIcon.setVisibility(View.GONE);
mDeviceListRecyclerView.setVisibility(View.GONE);
+ mTimeoutMessage.setVisibility(View.GONE);
mProgressBar.setVisibility(View.GONE);
mBorderBottom.setVisibility(View.GONE);
}
diff --git a/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/DeviceListAdapter.java b/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/DeviceListAdapter.java
index d8348d1c9218..8f12cb4f99e6 100644
--- a/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/DeviceListAdapter.java
+++ b/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/DeviceListAdapter.java
@@ -101,7 +101,7 @@ class DeviceListAdapter extends RecyclerView.Adapter<DeviceListAdapter.ViewHolde
void setDevices(List<DeviceFilterPair<?>> devices) {
mDevices = devices;
- notifyDataSetChanged();
+ notifyItemRangeInserted(devices.size(), mDevices.size());
}
static class ViewHolder extends RecyclerView.ViewHolder {
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/Autopilot.kt b/packages/EasterEgg/src/com/android/egg/landroid/Autopilot.kt
index 8214c540304e..a4803d248ea8 100644
--- a/packages/EasterEgg/src/com/android/egg/landroid/Autopilot.kt
+++ b/packages/EasterEgg/src/com/android/egg/landroid/Autopilot.kt
@@ -44,7 +44,7 @@ class Autopilot(val ship: Spacecraft, val universe: Universe) : Entity {
if (enabled)
listOf(
"---- AUTOPILOT ENGAGED ----",
- "TGT: " + (target?.name?.toUpperCase() ?: "SELECTING..."),
+ "TGT: " + (target?.name?.uppercase() ?: "SELECTING..."),
"EXE: $strategy" + if (debug.isNotEmpty()) " ($debug)" else "",
)
.joinToString("\n")
diff --git a/packages/EasterEgg/src/com/android/egg/landroid/MainActivity.kt b/packages/EasterEgg/src/com/android/egg/landroid/MainActivity.kt
index 95a60c7a5292..61ea08ecbf5d 100644
--- a/packages/EasterEgg/src/com/android/egg/landroid/MainActivity.kt
+++ b/packages/EasterEgg/src/com/android/egg/landroid/MainActivity.kt
@@ -236,8 +236,8 @@ fun Telemetry(universe: Universe, showControls: Boolean) {
((closest.pos - pos).mag() - closest.radius).toInt()
listOfNotNull(
landing?.let {
- "LND: ${it.planet.name.toUpperCase()}\n" +
- "JOB: ${it.text.toUpperCase()}"
+ "LND: ${it.planet.name.uppercase()}\n" +
+ "JOB: ${it.text.uppercase()}"
}
?: if (distToClosest < 10_000) {
"ALT: $distToClosest"
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/SettingsLib/CollapsingToolbarBaseActivity/res/layout-v36/settingslib_expressive_collapsing_toolbar_content_layout.xml b/packages/SettingsLib/CollapsingToolbarBaseActivity/res/layout-v36/settingslib_expressive_collapsing_toolbar_content_layout.xml
index 6221659388d1..3db0ac653848 100644
--- a/packages/SettingsLib/CollapsingToolbarBaseActivity/res/layout-v36/settingslib_expressive_collapsing_toolbar_content_layout.xml
+++ b/packages/SettingsLib/CollapsingToolbarBaseActivity/res/layout-v36/settingslib_expressive_collapsing_toolbar_content_layout.xml
@@ -41,6 +41,7 @@
android:id="@+id/action_bar"
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
+ android:layout_marginStart="@dimen/settingslib_expressive_space_extrasmall4"
android:theme="?android:attr/actionBarTheme"
android:transitionName="shared_element_view"
app:layout_collapseMode="pin"/>
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..d55bbb3bbdbf 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;
}
@@ -443,6 +447,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;
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/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/OWNERS b/packages/SettingsLib/OWNERS
index 04df308e72a0..7348920dfd3d 100644
--- a/packages/SettingsLib/OWNERS
+++ b/packages/SettingsLib/OWNERS
@@ -8,6 +8,7 @@ edgarwang@google.com
evanlaird@google.com
jiannan@google.com
juliacr@google.com
+millchen@google.com
ykhung@google.com
# Exempt resource files (because they are in a flat directory and too hard to manage via OWNERS)
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/src/com/android/settingslib/widget/NormalPaddingMixin.kt b/packages/SettingsLib/SettingsTheme/src/com/android/settingslib/widget/NormalPaddingMixin.kt
new file mode 100644
index 000000000000..5035542b9783
--- /dev/null
+++ b/packages/SettingsLib/SettingsTheme/src/com/android/settingslib/widget/NormalPaddingMixin.kt
@@ -0,0 +1,25 @@
+/*
+ * 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.settingslib.widget
+
+/**
+ * A base interface to indicate that a Preference should have normal paddings.
+ *
+ * Preferences implementing this interface will be treated as has normal paddings both inside and
+ * outside.
+ */
+interface NormalPaddingMixin \ No newline at end of file
diff --git a/packages/SettingsLib/SettingsTheme/src/com/android/settingslib/widget/SettingsPreferenceGroupAdapter.kt b/packages/SettingsLib/SettingsTheme/src/com/android/settingslib/widget/SettingsPreferenceGroupAdapter.kt
index a04fce7eeb86..2672787a0519 100644
--- a/packages/SettingsLib/SettingsTheme/src/com/android/settingslib/widget/SettingsPreferenceGroupAdapter.kt
+++ b/packages/SettingsLib/SettingsTheme/src/com/android/settingslib/widget/SettingsPreferenceGroupAdapter.kt
@@ -177,14 +177,32 @@ open class SettingsPreferenceGroupAdapter(preferenceGroup: PreferenceGroup) :
val v = holder.itemView
// Update padding
if (SettingsThemeHelper.isExpressiveTheme(context)) {
- val paddingStart = if (backgroundRes == 0) mNormalPaddingStart else mGroupPaddingStart
- val paddingEnd = if (backgroundRes == 0) mNormalPaddingEnd else mGroupPaddingEnd
+ val (paddingStart, paddingEnd) = getStartEndPadding(position, backgroundRes)
v.setPaddingRelative(paddingStart, v.paddingTop, paddingEnd, v.paddingBottom)
+ v.clipToOutline = backgroundRes != 0
}
// Update background
v.setBackgroundResource(backgroundRes)
}
+ private fun getStartEndPadding(position: Int, backgroundRes: Int): Pair<Int, Int> {
+ val item = getItem(position)
+ return when {
+ // This item handles edge to edge itself
+ item is NormalPaddingMixin && item is GroupSectionDividerMixin -> 0 to 0
+
+ // According to mappingPreferenceGroup(), backgroundRes == 0 means this item is
+ // GroupSectionDividerMixin or PreferenceCategory, which is design to have normal
+ // padding.
+ // NormalPaddingMixin items are also designed to have normal padding.
+ backgroundRes == 0 || item is NormalPaddingMixin ->
+ mNormalPaddingStart to mNormalPaddingEnd
+
+ // Other items are suppose to have group padding.
+ else -> mGroupPaddingStart to mGroupPaddingEnd
+ }
+ }
+
@DrawableRes
protected fun getRoundCornerDrawableRes(position: Int, isSelected: Boolean): Int {
return getRoundCornerDrawableRes(position, isSelected, false)
diff --git a/packages/SettingsLib/SliderPreference/src/com/android/settingslib/widget/SliderPreference.java b/packages/SettingsLib/SliderPreference/src/com/android/settingslib/widget/SliderPreference.java
index 4315238ad7c1..fe8e8b6f1d46 100644
--- a/packages/SettingsLib/SliderPreference/src/com/android/settingslib/widget/SliderPreference.java
+++ b/packages/SettingsLib/SliderPreference/src/com/android/settingslib/widget/SliderPreference.java
@@ -22,6 +22,7 @@ import android.content.res.Resources;
import android.content.res.TypedArray;
import android.os.Parcel;
import android.os.Parcelable;
+import android.text.TextUtils;
import android.util.AttributeSet;
import android.util.Log;
import android.view.KeyEvent;
@@ -72,6 +73,7 @@ public class SliderPreference extends Preference {
private int mSliderIncrement;
private boolean mAdjustable;
private boolean mTrackingTouch;
+ private CharSequence mSliderContentDescription;
/**
* Listener reacting to the user pressing DPAD left/right keys if {@code
@@ -143,6 +145,7 @@ public class SliderPreference extends Preference {
@Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
setLayoutResource(R.layout.settingslib_expressive_preference_slider);
+ setSelectable(false);
TypedArray a = context.obtainStyledAttributes(
attrs, androidx.preference.R.styleable.SeekBarPreference, defStyleAttr,
@@ -265,6 +268,14 @@ public class SliderPreference extends Preference {
} else {
mSliderIncrement = (int) (mSlider.getStepSize());
}
+ final CharSequence title = getTitle();
+ if (!TextUtils.isEmpty(mSliderContentDescription)) {
+ mSlider.setContentDescription(mSliderContentDescription);
+ } else if (!TextUtils.isEmpty(title)) {
+ mSlider.setContentDescription(title);
+ } else {
+ mSlider.setContentDescription(null);
+ }
mSlider.setValueFrom(mMin);
mSlider.setValueTo(mMax);
mSlider.setValue(mSliderValue);
@@ -273,6 +284,8 @@ public class SliderPreference extends Preference {
mSlider.clearOnChangeListeners();
mSlider.addOnChangeListener(mChangeListener);
mSlider.setEnabled(isEnabled());
+ mSlider.setFocusable(isSelectable());
+ mSlider.setClickable(isSelectable());
// Set up slider color
mSlider.setTrackActiveTintList(mTrackActiveColor);
@@ -471,6 +484,19 @@ public class SliderPreference extends Preference {
setValueInternal(sliderValue, true);
}
+
+ /**
+ * Sets the content description of the {@link Slider}.
+ *
+ * @param contentDescription The content description of the {@link Slider}
+ */
+ public void setSliderContentDescription(@Nullable CharSequence contentDescription) {
+ mSliderContentDescription = contentDescription;
+ if (mSlider != null) {
+ mSlider.setContentDescription(contentDescription);
+ }
+ }
+
@Override
protected void onSetInitialValue(@Nullable Object defaultValue) {
if (defaultValue == null) {
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-fr/strings.xml b/packages/SettingsLib/res/values-fr/strings.xml
index 2fc946b9ed9f..07849e09ecc9 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>
diff --git a/packages/SettingsLib/res/values-mr/strings.xml b/packages/SettingsLib/res/values-mr/strings.xml
index 826d6015fc49..8c77b0897aed 100644
--- a/packages/SettingsLib/res/values-mr/strings.xml
+++ b/packages/SettingsLib/res/values-mr/strings.xml
@@ -561,7 +561,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-pt-rPT/strings.xml b/packages/SettingsLib/res/values-pt-rPT/strings.xml
index e4381d6131e7..11f410f90497 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>
diff --git a/packages/SettingsLib/res/values-vi/strings.xml b/packages/SettingsLib/res/values-vi/strings.xml
index 6095e8c2af4c..c2c035c15ce0 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>
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/RestrictedPreferenceHelper.java b/packages/SettingsLib/src/com/android/settingslib/RestrictedPreferenceHelper.java
index 1044750bae25..9d979019be58 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(
@@ -321,7 +321,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..3646842d36ef 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
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/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..ed53d8d04988 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;
@@ -2175,6 +2183,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/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/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/SecureSettings.java b/packages/SettingsProvider/src/android/provider/settings/backup/SecureSettings.java
index f692601edcb7..a2291123e192 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,
@@ -96,6 +97,7 @@ public class SecureSettings {
Settings.Secure.ACCESSIBILITY_AUTOCLICK_ENABLED,
Settings.Secure.ACCESSIBILITY_AUTOCLICK_DELAY,
Settings.Secure.ACCESSIBILITY_AUTOCLICK_PANEL_POSITION,
+ Settings.Secure.ACCESSIBILITY_AUTOCLICK_REVERT_TO_LEFT_CLICK,
Settings.Secure.ACCESSIBILITY_LARGE_POINTER_ICON,
Settings.Secure.PREFERRED_TTY_MODE,
Settings.Secure.ENHANCED_VOICE_PRIVACY_ENABLED,
@@ -228,6 +230,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,
@@ -265,13 +268,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,
@@ -294,5 +296,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/validators/SecureSettingsValidators.java b/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java
index e42d3fb9da41..a4325344709a 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);
@@ -147,6 +145,7 @@ public class SecureSettingsValidators {
VALIDATORS.put(Secure.ACCESSIBILITY_AUTOCLICK_ENABLED, BOOLEAN_VALIDATOR);
VALIDATORS.put(Secure.ACCESSIBILITY_AUTOCLICK_DELAY, NON_NEGATIVE_INTEGER_VALIDATOR);
VALIDATORS.put(Secure.ACCESSIBILITY_AUTOCLICK_PANEL_POSITION, ANY_STRING_VALIDATOR);
+ VALIDATORS.put(Secure.ACCESSIBILITY_AUTOCLICK_REVERT_TO_LEFT_CLICK, BOOLEAN_VALIDATOR);
VALIDATORS.put(Secure.ACCESSIBILITY_LARGE_POINTER_ICON, BOOLEAN_VALIDATOR);
VALIDATORS.put(
Secure.PREFERRED_TTY_MODE,
@@ -325,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,
@@ -431,6 +434,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"}));
@@ -463,5 +468,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/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/SettingsProtoDumpUtil.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java
index c29a5a20a96f..57facdaa388c 100644
--- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java
+++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java
@@ -1722,6 +1722,9 @@ class SettingsProtoDumpUtil {
Settings.Secure.ACCESSIBILITY_AUTOCLICK_IGNORE_MINOR_CURSOR_MOVEMENT,
SecureSettingsProto.Accessibility.AUTOCLICK_IGNORE_MINOR_CURSOR_MOVEMENT);
dumpSetting(s, p,
+ Settings.Secure.ACCESSIBILITY_AUTOCLICK_REVERT_TO_LEFT_CLICK,
+ SecureSettingsProto.Accessibility.AUTOCLICK_REVERT_TO_LEFT_CLICK);
+ dumpSetting(s, p,
Settings.Secure.ACCESSIBILITY_AUTOCLICK_ENABLED,
SecureSettingsProto.Accessibility.AUTOCLICK_ENABLED);
dumpSetting(s, p,
@@ -1878,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);
@@ -2186,15 +2193,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);
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/OWNERS b/packages/Shell/OWNERS
index f7b8f8a8c6c5..897c1fe7639e 100644
--- a/packages/Shell/OWNERS
+++ b/packages/Shell/OWNERS
@@ -4,11 +4,9 @@ ronish@google.com
jsharkey@android.com
felipeal@google.com
nandana@google.com
-svetoslavganov@google.com
hackbod@google.com
yamasani@google.com
patb@google.com
-cbrubaker@google.com
omakoto@google.com
michaelwr@google.com
ronish@google.com
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/SystemUI/Android.bp b/packages/SystemUI/Android.bp
index b53cb27dd73b..5b48566d92f9 100644
--- a/packages/SystemUI/Android.bp
+++ b/packages/SystemUI/Android.bp
@@ -727,6 +727,7 @@ android_library {
"TraceurCommon",
"Traceur-res",
"aconfig_settings_flags_lib",
+ "kairos",
],
}
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/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..3c86f28cccb3 100644
--- a/packages/SystemUI/aconfig/systemui.aconfig
+++ b/packages/SystemUI/aconfig/systemui.aconfig
@@ -292,6 +292,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."
@@ -1434,16 +1445,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"
@@ -2103,3 +2104,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..e43b8a0b9297 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
@@ -1514,6 +1515,20 @@ constructor(
)
}
+ if (moveTransitionAnimationLayer()) {
+ // 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 +1581,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 +1706,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/TextAnimator.kt b/packages/SystemUI/animation/src/com/android/systemui/animation/TextAnimator.kt
index 5b073e49192a..4a39cff388a9 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
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..5d9c441db003 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
@@ -1183,6 +1186,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/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/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/compose/core/src/com/android/compose/PlatformButtons.kt b/packages/SystemUI/compose/core/src/com/android/compose/PlatformButtons.kt
index 20efea513b3a..df50eb8fa3e8 100644
--- a/packages/SystemUI/compose/core/src/com/android/compose/PlatformButtons.kt
+++ b/packages/SystemUI/compose/core/src/com/android/compose/PlatformButtons.kt
@@ -31,6 +31,7 @@ import androidx.compose.material3.IconButtonDefaults
import androidx.compose.material3.MaterialTheme
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
+import androidx.compose.ui.graphics.Shape
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.unit.dp
@@ -40,12 +41,15 @@ fun PlatformButton(
modifier: Modifier = Modifier,
enabled: Boolean = true,
colors: ButtonColors = filledButtonColors(),
+ contentPadding: PaddingValues = ButtonPaddings,
+ shape: Shape = ButtonDefaults.shape,
content: @Composable RowScope.() -> Unit,
) {
androidx.compose.material3.Button(
modifier = modifier.heightIn(min = 36.dp),
colors = colors,
- contentPadding = ButtonPaddings,
+ contentPadding = contentPadding,
+ shape = shape,
onClick = onClick,
enabled = enabled,
) {
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/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/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/CommunalContent.kt b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalContent.kt
index 2c6d09a4593a..2d03e2bcdd19 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalContent.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalContent.kt
@@ -23,7 +23,6 @@ import androidx.compose.material3.MaterialTheme
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.layout.Layout
-import androidx.compose.ui.layout.Measurable
import androidx.compose.ui.unit.Constraints
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.IntRect
@@ -35,7 +34,6 @@ import com.android.systemui.communal.smartspace.SmartspaceInteractionHandler
import com.android.systemui.communal.ui.compose.section.AmbientStatusBarSection
import com.android.systemui.communal.ui.compose.section.CommunalLockSection
import com.android.systemui.communal.ui.compose.section.CommunalPopupSection
-import com.android.systemui.communal.ui.compose.section.CommunalToDreamButtonSection
import com.android.systemui.communal.ui.compose.section.HubOnboardingSection
import com.android.systemui.communal.ui.view.layout.sections.CommunalAppWidgetSection
import com.android.systemui.communal.ui.viewmodel.CommunalViewModel
@@ -61,7 +59,6 @@ constructor(
private val ambientStatusBarSection: AmbientStatusBarSection,
private val communalPopupSection: CommunalPopupSection,
private val widgetSection: CommunalAppWidgetSection,
- private val communalToDreamButtonSection: CommunalToDreamButtonSection,
private val hubOnboardingSection: HubOnboardingSection,
) {
@@ -103,13 +100,11 @@ constructor(
Modifier.element(Communal.Elements.IndicationArea).fillMaxWidth()
)
}
- with(communalToDreamButtonSection) { Button() }
},
) { measurables, constraints ->
val communalGridMeasurable = measurables[0]
val lockIconMeasurable = measurables[1]
val bottomAreaMeasurable = measurables[2]
- val screensaverButtonMeasurable: Measurable? = measurables.getOrNull(3)
val noMinConstraints = constraints.copy(minWidth = 0, minHeight = 0)
@@ -152,9 +147,6 @@ constructor(
val bottomAreaPlaceable = bottomAreaMeasurable.measure(noMinConstraints)
- val screensaverButtonPlaceable =
- screensaverButtonMeasurable?.measure(noMinConstraints)
-
val communalGridPlaceable =
communalGridMeasurable.measure(
noMinConstraints.copy(maxHeight = lockIconBounds.top)
@@ -166,26 +158,12 @@ constructor(
val bottomAreaTop = constraints.maxHeight - bottomAreaPlaceable.height
bottomAreaPlaceable.place(x = 0, y = bottomAreaTop)
-
- val screensaverButtonPaddingInt = screensaverButtonPadding.roundToPx()
- screensaverButtonPlaceable?.place(
- x =
- constraints.maxWidth -
- screensaverButtonPaddingInt -
- screensaverButtonPlaceable.width,
- y =
- constraints.maxHeight -
- screensaverButtonPaddingInt -
- screensaverButtonPlaceable.height,
- )
}
}
}
}
companion object {
- private val screensaverButtonPadding: Dp = 24.dp
-
// TODO(b/382739998): Remove these hardcoded values once lock icon size and bottom area
// position are sorted.
private val lockIconSize: Dp = 54.dp
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/section/CommunalToDreamButtonSection.kt b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/section/CommunalToDreamButtonSection.kt
deleted file mode 100644
index acaf43a62f43..000000000000
--- a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/section/CommunalToDreamButtonSection.kt
+++ /dev/null
@@ -1,231 +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.ui.compose.section
-
-import androidx.compose.animation.AnimatedVisibility
-import androidx.compose.animation.core.MutableTransitionState
-import androidx.compose.animation.expandVertically
-import androidx.compose.animation.fadeIn
-import androidx.compose.animation.fadeOut
-import androidx.compose.animation.shrinkVertically
-import androidx.compose.foundation.layout.Column
-import androidx.compose.foundation.layout.Spacer
-import androidx.compose.foundation.layout.height
-import androidx.compose.foundation.layout.padding
-import androidx.compose.foundation.layout.width
-import androidx.compose.foundation.layout.widthIn
-import androidx.compose.foundation.shape.CornerSize
-import androidx.compose.material3.IconButtonDefaults
-import androidx.compose.material3.MaterialTheme
-import androidx.compose.material3.Surface
-import androidx.compose.material3.Text
-import androidx.compose.runtime.Composable
-import androidx.compose.runtime.LaunchedEffect
-import androidx.compose.runtime.getValue
-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.geometry.CornerRadius
-import androidx.compose.ui.geometry.Offset
-import androidx.compose.ui.geometry.RoundRect
-import androidx.compose.ui.geometry.Size
-import androidx.compose.ui.geometry.toRect
-import androidx.compose.ui.graphics.Outline
-import androidx.compose.ui.graphics.Path
-import androidx.compose.ui.graphics.Shape
-import androidx.compose.ui.input.pointer.pointerInput
-import androidx.compose.ui.res.dimensionResource
-import androidx.compose.ui.res.stringResource
-import androidx.compose.ui.unit.Density
-import androidx.compose.ui.unit.Dp
-import androidx.compose.ui.unit.LayoutDirection
-import androidx.compose.ui.unit.dp
-import com.android.compose.PlatformIconButton
-import com.android.systemui.communal.domain.interactor.CommunalSettingsInteractor
-import com.android.systemui.communal.ui.compose.extensions.observeTaps
-import com.android.systemui.communal.ui.viewmodel.CommunalToDreamButtonViewModel
-import com.android.systemui.lifecycle.rememberViewModel
-import com.android.systemui.res.R
-import javax.inject.Inject
-import kotlin.time.Duration.Companion.seconds
-import kotlinx.coroutines.delay
-
-class CommunalToDreamButtonSection
-@Inject
-constructor(
- private val communalSettingsInteractor: CommunalSettingsInteractor,
- private val viewModelFactory: CommunalToDreamButtonViewModel.Factory,
-) {
- @Composable
- fun Button() {
- if (!communalSettingsInteractor.isV2FlagEnabled()) {
- return
- }
-
- val viewModel =
- rememberViewModel("CommunalToDreamButtonSection") { viewModelFactory.create() }
-
- if (!viewModel.shouldShowDreamButtonOnHub) {
- return
- }
-
- val buttonSize = dimensionResource(R.dimen.communal_to_dream_button_size)
-
- if (viewModel.shouldShowTooltip) {
- val tooltipVisibleState = remember { MutableTransitionState(false) }
-
- Column(
- modifier =
- Modifier.widthIn(max = tooltipMaxWidth).pointerInput(Unit) {
- observeTaps {
- if (tooltipVisibleState.isCurrentlyVisible()) {
- tooltipVisibleState.targetState = false
- }
- }
- }
- ) {
- var waitingToShowTooltip by remember { mutableStateOf(true) }
-
- LaunchedEffect(tooltipVisibleState.targetState) {
- delay(3.seconds)
- tooltipVisibleState.targetState = true
- waitingToShowTooltip = false
- }
-
- // This LaunchedEffect is used to wait for the tooltip dismiss animation to
- // complete before setting the tooltip dismissed. Otherwise, the composable would
- // be removed before the animation can start.
- LaunchedEffect(
- tooltipVisibleState.currentState,
- tooltipVisibleState.isIdle,
- waitingToShowTooltip,
- ) {
- if (
- !waitingToShowTooltip &&
- !tooltipVisibleState.currentState &&
- tooltipVisibleState.isIdle
- ) {
- viewModel.setDreamButtonTooltipDismissed()
- }
- }
-
- AnimatedVisibility(
- visibleState = tooltipVisibleState,
- enter = fadeIn() + expandVertically(expandFrom = Alignment.Bottom),
- exit = fadeOut() + shrinkVertically(shrinkTowards = Alignment.Bottom),
- ) {
- Tooltip(
- pointerOffsetDp = buttonSize.div(2),
- text = stringResource(R.string.glanceable_hub_to_dream_button_tooltip),
- )
- }
-
- GoToDreamButton(
- modifier = Modifier.width(buttonSize).height(buttonSize).align(Alignment.End)
- ) {
- viewModel.onShowDreamButtonTap()
- }
- }
- } else {
- GoToDreamButton(modifier = Modifier.width(buttonSize).height(buttonSize)) {
- viewModel.onShowDreamButtonTap()
- }
- }
- }
-
- private fun MutableTransitionState<Boolean>.isCurrentlyVisible() = currentState && isIdle
-
- companion object {
- private val tooltipMaxWidth = 350.dp
- }
-}
-
-@Composable
-private fun GoToDreamButton(modifier: Modifier, onClick: () -> Unit) {
- PlatformIconButton(
- modifier = modifier,
- onClick = onClick,
- iconResource = R.drawable.ic_screensaver_auto,
- contentDescription = stringResource(R.string.accessibility_glanceable_hub_to_dream_button),
- colors =
- IconButtonDefaults.filledIconButtonColors(
- contentColor = MaterialTheme.colorScheme.onPrimaryContainer,
- containerColor = MaterialTheme.colorScheme.primaryContainer,
- ),
- )
-}
-
-@Composable
-private fun Tooltip(pointerOffsetDp: Dp, text: String) {
- Surface(
- color = MaterialTheme.colorScheme.surface,
- shape = TooltipShape(pointerSizeDp = 12.dp, pointerOffsetDp = pointerOffsetDp),
- ) {
- Text(
- modifier = Modifier.padding(start = 32.dp, top = 16.dp, end = 32.dp, bottom = 32.dp),
- color = MaterialTheme.colorScheme.onSurface,
- text = text,
- )
- }
-
- Spacer(modifier = Modifier.height(4.dp))
-}
-
-private class TooltipShape(private val pointerSizeDp: Dp, private val pointerOffsetDp: Dp) : Shape {
-
- override fun createOutline(
- size: Size,
- layoutDirection: LayoutDirection,
- density: Density,
- ): Outline {
-
- val pointerSizePx = with(density) { pointerSizeDp.toPx() }
- val pointerOffsetPx = with(density) { pointerOffsetDp.toPx() }
- val cornerRadius = CornerRadius(CornerSize(16.dp).toPx(size, density))
- val bubbleSize = size.copy(height = size.height - pointerSizePx)
-
- val path =
- Path().apply {
- addRoundRect(
- RoundRect(
- rect = bubbleSize.toRect(),
- topLeft = cornerRadius,
- topRight = cornerRadius,
- bottomRight = cornerRadius,
- bottomLeft = cornerRadius,
- )
- )
- addPath(
- Path().apply {
- moveTo(0f, 0f)
- lineTo(pointerSizePx / 2f, pointerSizePx)
- lineTo(pointerSizePx, 0f)
- close()
- },
- offset =
- Offset(
- x = bubbleSize.width - pointerOffsetPx - pointerSizePx / 2f,
- y = bubbleSize.height,
- ),
- )
- }
-
- return Outline.Generic(path)
- }
-}
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/notifications/ui/composable/row/BundleHeader.kt b/packages/SystemUI/compose/features/src/com/android/systemui/notifications/ui/composable/row/BundleHeader.kt
new file mode 100644
index 000000000000..d7740a426513
--- /dev/null
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/notifications/ui/composable/row/BundleHeader.kt
@@ -0,0 +1,193 @@
+/*
+ * 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.notifications.ui.composable.row
+
+import android.content.Context
+import android.graphics.drawable.Drawable
+import androidx.compose.foundation.Image
+import androidx.compose.foundation.clickable
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.Row
+import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.layout.size
+import androidx.compose.material3.ExperimentalMaterial3ExpressiveApi
+import androidx.compose.material3.MaterialTheme
+import androidx.compose.material3.Text
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.rememberCoroutineScope
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.layout.ContentScale
+import androidx.compose.ui.layout.Layout
+import androidx.compose.ui.platform.ComposeView
+import androidx.compose.ui.text.style.TextOverflow
+import androidx.compose.ui.unit.constrainHeight
+import androidx.compose.ui.unit.constrainWidth
+import androidx.compose.ui.unit.dp
+import androidx.compose.ui.util.fastForEach
+import androidx.compose.ui.util.fastMap
+import androidx.compose.ui.util.fastMaxOfOrDefault
+import androidx.compose.ui.util.fastSumBy
+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.theme.PlatformTheme
+import com.android.compose.ui.graphics.painter.rememberDrawablePainter
+import com.android.systemui.statusbar.notification.row.ui.viewmodel.BundleHeaderViewModel
+
+object BundleHeader {
+ object Scenes {
+ val Collapsed = SceneKey("Collapsed")
+ val Expanded = SceneKey("Expanded")
+ }
+
+ object Elements {
+ val PreviewIcon1 = ElementKey("PreviewIcon1")
+ val PreviewIcon2 = ElementKey("PreviewIcon2")
+ val PreviewIcon3 = ElementKey("PreviewIcon3")
+ val TitleText = ElementKey("TitleText")
+ }
+}
+
+fun createComposeView(viewModel: BundleHeaderViewModel, context: Context): ComposeView {
+ // TODO(b/399588047): Check if we can init PlatformTheme once instead of once per ComposeView
+ return ComposeView(context).apply { setContent { PlatformTheme { BundleHeader(viewModel) } } }
+}
+
+@Composable
+fun BundleHeader(viewModel: BundleHeaderViewModel, modifier: Modifier = Modifier) {
+ Box(modifier) {
+ Background(background = viewModel.backgroundDrawable, modifier = Modifier.matchParentSize())
+ val scope = rememberCoroutineScope()
+ SceneTransitionLayout(
+ state = viewModel.state,
+ modifier =
+ Modifier.clickable(
+ onClick = { viewModel.onHeaderClicked(scope) },
+ interactionSource = null,
+ indication = null,
+ ),
+ ) {
+ scene(BundleHeader.Scenes.Collapsed) {
+ BundleHeaderContent(viewModel, collapsed = true)
+ }
+ scene(BundleHeader.Scenes.Expanded) {
+ BundleHeaderContent(viewModel, collapsed = false)
+ }
+ }
+ }
+}
+
+@Composable
+private fun Background(background: Drawable?, modifier: Modifier = Modifier) {
+ if (background != null) {
+ val painter = rememberDrawablePainter(drawable = background)
+ Image(
+ painter = painter,
+ contentDescription = null,
+ contentScale = ContentScale.Crop,
+ modifier = modifier,
+ )
+ }
+}
+
+@OptIn(ExperimentalMaterial3ExpressiveApi::class)
+@Composable
+private fun ContentScope.BundleHeaderContent(
+ viewModel: BundleHeaderViewModel,
+ collapsed: Boolean,
+ modifier: Modifier = Modifier,
+) {
+ Row(
+ verticalAlignment = Alignment.CenterVertically,
+ modifier = modifier.padding(vertical = 16.dp),
+ ) {
+ BundleIcon(viewModel.bundleIcon, modifier = Modifier.padding(horizontal = 16.dp))
+ Text(
+ text = viewModel.titleText,
+ style = MaterialTheme.typography.titleMediumEmphasized,
+ color = MaterialTheme.colorScheme.primary,
+ overflow = TextOverflow.Ellipsis,
+ maxLines = 1,
+ modifier = Modifier.element(BundleHeader.Elements.TitleText).weight(1f),
+ )
+
+ if (collapsed && viewModel.previewIcons.isNotEmpty()) {
+ BundlePreviewIcons(
+ previewDrawables = viewModel.previewIcons,
+ modifier = Modifier.padding(start = 8.dp),
+ )
+ }
+
+ ExpansionControl(
+ collapsed = collapsed,
+ hasUnread = viewModel.hasUnreadMessages,
+ numberToShow = viewModel.numberOfChildren,
+ modifier = Modifier.padding(start = 8.dp, end = 16.dp),
+ )
+ }
+}
+
+@Composable
+private fun ContentScope.BundlePreviewIcons(
+ previewDrawables: List<Drawable>,
+ modifier: Modifier = Modifier,
+) {
+ check(previewDrawables.isNotEmpty())
+ val iconSize = 32.dp
+ HalfOverlappingReversedRow(modifier = modifier) {
+ PreviewIcon(
+ drawable = previewDrawables[0],
+ modifier = Modifier.element(BundleHeader.Elements.PreviewIcon1).size(iconSize),
+ )
+ if (previewDrawables.size < 2) return@HalfOverlappingReversedRow
+ PreviewIcon(
+ drawable = previewDrawables[1],
+ modifier = Modifier.element(BundleHeader.Elements.PreviewIcon2).size(iconSize),
+ )
+ if (previewDrawables.size < 3) return@HalfOverlappingReversedRow
+ PreviewIcon(
+ drawable = previewDrawables[2],
+ modifier = Modifier.element(BundleHeader.Elements.PreviewIcon3).size(iconSize),
+ )
+ }
+}
+
+@Composable
+private fun HalfOverlappingReversedRow(
+ modifier: Modifier = Modifier,
+ content: @Composable () -> Unit,
+) {
+ Layout(modifier = modifier, content = content) { measurables, constraints ->
+ val placeables = measurables.fastMap { measurable -> measurable.measure(constraints) }
+
+ if (placeables.isEmpty())
+ return@Layout layout(constraints.minWidth, constraints.minHeight) {}
+ val width = placeables.fastSumBy { it.width / 2 } + placeables.first().width / 2
+ val childHeight = placeables.fastMaxOfOrDefault(0) { it.height }
+
+ layout(constraints.constrainWidth(width), constraints.constrainHeight(childHeight)) {
+ // Start in the middle of the right-most placeable
+ var currentXPosition = placeables.fastSumBy { it.width / 2 }
+ placeables.fastForEach { placeable ->
+ currentXPosition -= placeable.width / 2
+ placeable.placeRelative(x = currentXPosition, y = 0)
+ }
+ }
+ }
+}
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/notifications/ui/composable/row/NotificationRowPrimitives.kt b/packages/SystemUI/compose/features/src/com/android/systemui/notifications/ui/composable/row/NotificationRowPrimitives.kt
new file mode 100644
index 000000000000..c9ffa401002a
--- /dev/null
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/notifications/ui/composable/row/NotificationRowPrimitives.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.notifications.ui.composable.row
+
+import android.graphics.drawable.Drawable
+import androidx.compose.foundation.Image
+import androidx.compose.foundation.background
+import androidx.compose.foundation.border
+import androidx.compose.foundation.isSystemInDarkTheme
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.Row
+import androidx.compose.foundation.layout.fillMaxSize
+import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.layout.size
+import androidx.compose.foundation.shape.CircleShape
+import androidx.compose.material.icons.Icons
+import androidx.compose.material.icons.filled.ExpandMore
+import androidx.compose.material3.ExperimentalMaterial3ExpressiveApi
+import androidx.compose.material3.Icon
+import androidx.compose.material3.MaterialTheme
+import androidx.compose.material3.Text
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.ReadOnlyComposable
+import androidx.compose.runtime.getValue
+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.graphics.Color
+import androidx.compose.ui.graphics.ColorFilter
+import androidx.compose.ui.graphics.graphicsLayer
+import androidx.compose.ui.layout.ContentScale
+import androidx.compose.ui.platform.LocalDensity
+import androidx.compose.ui.unit.dp
+import androidx.compose.ui.unit.sp
+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.animation.scene.ValueKey
+import com.android.compose.animation.scene.animateElementColorAsState
+import com.android.compose.animation.scene.animateElementFloatAsState
+import com.android.compose.ui.graphics.painter.rememberDrawablePainter
+
+object NotificationRowPrimitives {
+ object Elements {
+ val PillBackground = ElementKey("PillBackground", contentPicker = LowestZIndexContentPicker)
+ val NotificationIconBackground = ElementKey("NotificationIconBackground")
+ val Chevron = ElementKey("Chevron")
+ }
+
+ object Values {
+ val ChevronRotation = ValueKey("NotificationChevronRotation")
+ val PillBackgroundColor = ValueKey("PillBackgroundColor")
+ }
+}
+
+/** The Icon displayed at the start of any notification row. */
+@Composable
+fun ContentScope.BundleIcon(drawable: Drawable?, modifier: Modifier = Modifier) {
+ val surfaceColor = notificationElementSurfaceColor()
+ Box(
+ modifier =
+ modifier
+ // Has to be a shared element because we may have semi-transparent background color
+ .element(NotificationRowPrimitives.Elements.NotificationIconBackground)
+ .size(40.dp)
+ .background(color = surfaceColor, shape = CircleShape)
+ ) {
+ if (drawable == null) return@Box
+ val painter = rememberDrawablePainter(drawable)
+ Image(
+ painter = painter,
+ contentDescription = null,
+ modifier = Modifier.padding(10.dp).fillMaxSize(),
+ contentScale = ContentScale.Fit,
+ colorFilter = ColorFilter.tint(MaterialTheme.colorScheme.primary),
+ )
+ }
+}
+
+/** The Icon used to display a preview of contained child notifications in a Bundle. */
+@Composable
+fun PreviewIcon(drawable: Drawable, modifier: Modifier = Modifier) {
+ val surfaceColor = notificationElementSurfaceColor()
+ Box(
+ modifier =
+ modifier
+ .background(color = surfaceColor, shape = CircleShape)
+ .border(0.5.dp, surfaceColor, CircleShape)
+ ) {
+ val painter = rememberDrawablePainter(drawable)
+ Image(
+ painter = painter,
+ contentDescription = null,
+ modifier = Modifier.fillMaxSize().clip(CircleShape),
+ contentScale = ContentScale.Fit,
+ )
+ }
+}
+
+/** The ExpansionControl of any expandable notification row, containing a Chevron. */
+@OptIn(ExperimentalMaterial3ExpressiveApi::class)
+@Composable
+fun ContentScope.ExpansionControl(
+ collapsed: Boolean,
+ hasUnread: Boolean,
+ numberToShow: Int?,
+ modifier: Modifier = Modifier,
+) {
+ val textColor =
+ if (hasUnread) MaterialTheme.colorScheme.onTertiary else MaterialTheme.colorScheme.onSurface
+ Box(modifier = modifier) {
+ // The background is a shared Element and therefore can't be the parent of a different
+ // shared Element (the chevron), otherwise the child can't be animated.
+ PillBackground(hasUnread, modifier = Modifier.matchParentSize())
+ Row(
+ verticalAlignment = Alignment.CenterVertically,
+ modifier = Modifier.padding(vertical = 2.dp, horizontal = 6.dp),
+ ) {
+ val iconSizeDp = with(LocalDensity.current) { 16.sp.toDp() }
+
+ if (numberToShow != null) {
+ Text(
+ text = numberToShow.toString(),
+ style = MaterialTheme.typography.labelSmallEmphasized,
+ color = textColor,
+ modifier = Modifier.padding(end = 2.dp),
+ )
+ }
+ Chevron(collapsed = collapsed, modifier = Modifier.size(iconSizeDp), color = textColor)
+ }
+ }
+}
+
+@Composable
+private fun ContentScope.PillBackground(hasUnread: Boolean, modifier: Modifier = Modifier) {
+ ElementWithValues(NotificationRowPrimitives.Elements.PillBackground, modifier) {
+ val bgColorNoUnread = notificationElementSurfaceColor()
+ val surfaceColor by
+ animateElementColorAsState(
+ if (hasUnread) MaterialTheme.colorScheme.tertiary else bgColorNoUnread,
+ NotificationRowPrimitives.Values.PillBackgroundColor,
+ )
+ content {
+ Box(
+ modifier =
+ Modifier.drawBehind {
+ drawRoundRect(
+ color = surfaceColor,
+ cornerRadius = CornerRadius(100.dp.toPx(), 100.dp.toPx()),
+ )
+ }
+ )
+ }
+ }
+}
+
+@Composable
+@ReadOnlyComposable
+private fun notificationElementSurfaceColor(): Color {
+ return if (isSystemInDarkTheme()) {
+ Color.White.copy(alpha = 0.15f)
+ } else {
+ MaterialTheme.colorScheme.surfaceContainerHighest
+ }
+}
+
+@Composable
+private fun ContentScope.Chevron(collapsed: Boolean, color: Color, modifier: Modifier = Modifier) {
+ val key = NotificationRowPrimitives.Elements.Chevron
+ ElementWithValues(key, modifier) {
+ val rotation by
+ animateElementFloatAsState(
+ if (collapsed) 0f else 180f,
+ NotificationRowPrimitives.Values.ChevronRotation,
+ )
+ content {
+ Icon(
+ imageVector = Icons.Default.ExpandMore,
+ contentDescription = null,
+ modifier = Modifier.graphicsLayer { rotationZ = rotation },
+ tint = color,
+ )
+ }
+ }
+}
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..216f0a74e1c7 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
@@ -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,
@@ -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 0f1cb409439f..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
@@ -26,16 +26,19 @@ import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.fillMaxWidth
-import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.requiredHeight
import androidx.compose.foundation.rememberScrollState
+import androidx.compose.foundation.systemGestureExclusion
import androidx.compose.foundation.verticalScroll
import androidx.compose.runtime.Composable
import androidx.compose.runtime.DisposableEffect
import androidx.compose.runtime.getValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
+import androidx.compose.ui.geometry.Offset
+import androidx.compose.ui.geometry.Rect
+import androidx.compose.ui.geometry.Size
import androidx.compose.ui.graphics.graphicsLayer
import androidx.compose.ui.layout.boundsInWindow
import androidx.compose.ui.layout.onPlaced
@@ -46,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
@@ -59,6 +65,7 @@ import com.android.systemui.qs.panels.ui.compose.EditMode
import com.android.systemui.qs.panels.ui.compose.TileDetails
import com.android.systemui.qs.panels.ui.compose.TileGrid
import com.android.systemui.qs.panels.ui.compose.toolbar.Toolbar
+import com.android.systemui.qs.ui.composable.QuickSettingsShade.systemGestureExclusionInShade
import com.android.systemui.qs.ui.viewmodel.QuickSettingsContainerViewModel
import com.android.systemui.qs.ui.viewmodel.QuickSettingsShadeOverlayActionsViewModel
import com.android.systemui.qs.ui.viewmodel.QuickSettingsShadeOverlayContentViewModel
@@ -251,13 +258,18 @@ fun ContentScope.QuickSettingsLayout(
modifier = Modifier.padding(horizontal = QuickSettingsShade.Dimensions.Padding),
)
- BrightnessSliderContainer(
- viewModel = viewModel.brightnessSliderViewModel,
- containerColor = OverlayShade.Colors.PanelBackground,
- modifier =
- Modifier.fillMaxWidth()
- .height(QuickSettingsShade.Dimensions.BrightnessSliderHeight),
- )
+ Box(
+ Modifier.systemGestureExclusionInShade(
+ enabled = { layoutState.transitionState is TransitionState.Idle }
+ )
+ ) {
+ BrightnessSliderContainer(
+ viewModel = viewModel.brightnessSliderViewModel,
+ containerColors =
+ ContainerColors.singleColor(OverlayShade.Colors.PanelBackground),
+ modifier = Modifier.fillMaxWidth(),
+ )
+ }
Box {
GridAnchor()
@@ -280,6 +292,27 @@ object QuickSettingsShade {
object Dimensions {
val Padding = 16.dp
val ToolbarHeight = 48.dp
- val BrightnessSliderHeight = 64.dp
+ }
+
+ /**
+ * Applies system gesture exclusion to a component adding [Dimensions.Padding] to left and
+ * right.
+ */
+ @Composable
+ fun Modifier.systemGestureExclusionInShade(enabled: () -> Boolean): Modifier {
+ val density = LocalDensity.current
+ 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..7015f79e4a9f 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()
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..9b45ef693ce6 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.EdgeContainerExpansionSpec
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: EdgeContainerExpansionSpec,
+ 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..eb5548d45247 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.EdgeContainerExpansionSpec
/**
* 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: EdgeContainerExpansionSpec,
+ 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: EdgeContainerExpansionSpec,
+ 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..9b4b91eb23c1 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.EdgeContainerExpansionSpec
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: EdgeContainerExpansionSpec,
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..47dd85f17cee 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.EdgeContainerExpansionSpec
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: EdgeContainerExpansionSpec,
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..3446081fb123 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.EdgeContainerExpansionSpec
+import com.android.mechanics.behavior.edgeContainerExpansionBackground
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()
+ .edgeContainerExpansionBackground(
+ OverlayShade.Colors.PanelBackground,
+ rememberShadeExpansionMotion(),
+ )
) {
- 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,13 @@ 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(): EdgeContainerExpansionSpec {
+ val radius = Dimensions.PanelCornerRadius
+ return remember(radius) { EdgeContainerExpansionSpec(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/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..72f9bd5da742 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.EdgeContainerExpansionSpec
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: EdgeContainerExpansionSpec,
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/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/goldens/verticalReveal_gesture_dragFullyClose.json b/packages/SystemUI/compose/scene/tests/goldens/verticalReveal_gesture_dragFullyClose.json
new file mode 100644
index 000000000000..57f67665242c
--- /dev/null
+++ b/packages/SystemUI/compose/scene/tests/goldens/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/verticalReveal_gesture_dragHalfClose.json b/packages/SystemUI/compose/scene/tests/goldens/verticalReveal_gesture_dragHalfClose.json
new file mode 100644
index 000000000000..01bc852cf7f4
--- /dev/null
+++ b/packages/SystemUI/compose/scene/tests/goldens/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/verticalReveal_gesture_dragOpen.json b/packages/SystemUI/compose/scene/tests/goldens/verticalReveal_gesture_dragOpen.json
new file mode 100644
index 000000000000..b6e423afc6c4
--- /dev/null
+++ b/packages/SystemUI/compose/scene/tests/goldens/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/verticalReveal_gesture_flingClose.json b/packages/SystemUI/compose/scene/tests/goldens/verticalReveal_gesture_flingClose.json
new file mode 100644
index 000000000000..a82db346ed58
--- /dev/null
+++ b/packages/SystemUI/compose/scene/tests/goldens/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/verticalReveal_gesture_flingOpen.json b/packages/SystemUI/compose/scene/tests/goldens/verticalReveal_gesture_flingOpen.json
new file mode 100644
index 000000000000..6dc5a0e79e81
--- /dev/null
+++ b/packages/SystemUI/compose/scene/tests/goldens/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/verticalReveal_gesture_magneticDetachAndReattach.json b/packages/SystemUI/compose/scene/tests/goldens/verticalReveal_gesture_magneticDetachAndReattach.json
new file mode 100644
index 000000000000..1cd971aa2898
--- /dev/null
+++ b/packages/SystemUI/compose/scene/tests/goldens/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/verticalReveal_triggeredRevealCloseTransition.json b/packages/SystemUI/compose/scene/tests/goldens/verticalReveal_triggeredRevealCloseTransition.json
new file mode 100644
index 000000000000..1030455e873f
--- /dev/null
+++ b/packages/SystemUI/compose/scene/tests/goldens/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/verticalReveal_triggeredRevealOpenTransition.json b/packages/SystemUI/compose/scene/tests/goldens/verticalReveal_triggeredRevealOpenTransition.json
new file mode 100644
index 000000000000..622c29eebfb4
--- /dev/null
+++ b/packages/SystemUI/compose/scene/tests/goldens/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/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..f4e2328f16ab
--- /dev/null
+++ b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/reveal/ContentRevealTest.kt
@@ -0,0 +1,279 @@
+/*
+ * 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 androidx.test.ext.junit.runners.AndroidJUnit4
+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.EdgeContainerExpansionSpec
+import com.android.mechanics.behavior.edgeContainerExpansionBackground
+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
+
+@RunWith(AndroidJUnit4::class)
+@MotionTest
+class ContentRevealTest {
+
+ private val goldenPaths =
+ createGoldenPathManager("frameworks/base/packages/SystemUI/compose/scene/tests/goldens")
+
+ @get:Rule val motionRule = createFixedConfigurationComposeMotionTestRule(goldenPaths)
+
+ private val fakeHaptics = FakeHaptics()
+
+ @Test
+ fun verticalReveal_triggeredRevealOpenTransition() {
+ assertVerticalContainerRevealMotion(TriggeredRevealMotion(SceneClosed, SceneOpen))
+ }
+
+ @Test
+ fun verticalReveal_triggeredRevealCloseTransition() {
+ assertVerticalContainerRevealMotion(TriggeredRevealMotion(SceneOpen, SceneClosed))
+ }
+
+ @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,
+ )
+ }
+ )
+ }
+
+ @Test
+ fun verticalReveal_gesture_dragOpen() {
+ assertVerticalContainerRevealMotion(
+ GestureRevealMotion(SceneClosed) {
+ swipeDown(endY = 200.dp.toPx(), durationMillis = 500)
+ }
+ )
+ }
+
+ @Test
+ fun verticalReveal_gesture_flingOpen() {
+ assertVerticalContainerRevealMotion(
+ GestureRevealMotion(SceneClosed) {
+ val end = Offset(centerX, 80.dp.toPx())
+ swipeWithVelocity(start = topCenter, end = end, endVelocity = FlingVelocity.toPx())
+ }
+ )
+ }
+
+ @Test
+ fun verticalReveal_gesture_dragFullyClose() {
+ assertVerticalContainerRevealMotion(
+ GestureRevealMotion(SceneOpen) {
+ swipeUp(200.dp.toPx(), 0.dp.toPx(), durationMillis = 500)
+ }
+ )
+ }
+
+ @Test
+ fun verticalReveal_gesture_dragHalfClose() {
+ assertVerticalContainerRevealMotion(
+ GestureRevealMotion(SceneOpen) {
+ swipeUp(350.dp.toPx(), 100.dp.toPx(), durationMillis = 500)
+ }
+ )
+ }
+
+ @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())
+ }
+ )
+ }
+
+ 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) =
+ 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()
+ }
+
+ @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)
+ .edgeContainerExpansionBackground(Color.DarkGray, MotionSpec)
+ )
+ }
+ }
+
+ private class FakeHaptics : ContainerRevealHaptics {
+ override fun onRevealThresholdCrossed(revealed: Boolean) {}
+ }
+
+ companion object {
+ 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")
+ val MotionSpec = EdgeContainerExpansionSpec()
+ }
+}
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..6e29e6932629 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
@@ -38,8 +38,9 @@ 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 +68,7 @@ constructor(
var messageBuffer: MessageBuffer
get() = logger.buffer
set(value) {
- logger = Logger(value, TAG)
+ logger = ClockLogger(this, value, TAG)
}
var hasCustomPositionUpdatedAnimation: Boolean = false
@@ -185,7 +186,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 +198,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 +228,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 +266,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 +283,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 +373,7 @@ constructor(
return
}
- logger.d("animateCharge")
+ logger.animateCharge()
val startAnimPhase2 = Runnable {
setTextStyle(
weight = if (isDozing()) dozingWeight else lockScreenWeight,
@@ -394,7 +397,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 +487,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 +637,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/ClockRegistry.kt b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/ClockRegistry.kt
index b59b4ab34c80..06484128ed6c 100644
--- a/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/ClockRegistry.kt
+++ b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/ClockRegistry.kt
@@ -103,6 +103,7 @@ open class ClockRegistry(
fun onAvailableClocksChanged() {}
}
+ private val replacementMap = ConcurrentHashMap<ClockId, ClockId>()
private val availableClocks = ConcurrentHashMap<ClockId, ClockInfo>()
private val clockChangeListeners = mutableListOf<ClockChangeListener>()
private val settingObserver =
@@ -209,6 +210,7 @@ open class ClockRegistry(
continue
}
+ clock.replacementTarget?.let { replacementMap[id] = it }
info.provider = plugin
onLoaded(info)
}
@@ -393,10 +395,11 @@ open class ClockRegistry(
// TODO: Merge w/ CurrentClockId when we convert to a flow. We shouldn't need both behaviors.
val activeClockId: String
get() {
- if (!availableClocks.containsKey(currentClockId)) {
+ var id = currentClockId
+ if (!availableClocks.containsKey(id)) {
return DEFAULT_CLOCK_ID
}
- return currentClockId
+ return replacementMap[id] ?: id
}
init {
@@ -404,6 +407,7 @@ open class ClockRegistry(
defaultClockProvider.initialize(clockBuffers)
for (clock in defaultClockProvider.getClocks()) {
availableClocks[clock.clockId] = ClockInfo(clock, defaultClockProvider, null)
+ clock.replacementTarget?.let { replacementMap[clock.clockId] = it }
}
// Something has gone terribly wrong if the default clock isn't present
@@ -562,9 +566,12 @@ open class ClockRegistry(
}
}
- fun getClocks(): List<ClockMetadata> {
- if (!isEnabled) return listOf(availableClocks[DEFAULT_CLOCK_ID]!!.metadata)
- return availableClocks.map { (_, clock) -> clock.metadata }
+ fun getClocks(includeDeprecated: Boolean = false): List<ClockMetadata> {
+ return when {
+ !isEnabled -> listOf(availableClocks[DEFAULT_CLOCK_ID]!!.metadata)
+ includeDeprecated -> availableClocks.map { (_, clock) -> clock.metadata }
+ else -> availableClocks.map { (_, clock) -> clock.metadata }.filter { !it.isDeprecated }
+ }
}
fun getClockPickerConfig(clockId: ClockId): ClockPickerConfig? {
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 b9200c15944c..37acbe261f76 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
@@ -44,6 +44,7 @@ class ComposedDigitalLayerController(private val clockCtx: ClockContext) :
val dozeState = DefaultClockController.AnimationState(1F)
override val view = FlexClockView(clockCtx)
+ override var onViewBoundsChanged by view::onViewBoundsChanged
init {
fun createController(cfg: LayerConfig) {
@@ -160,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 f24ad1c10c4a..bc4bdf4243cb 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
@@ -28,6 +28,7 @@ import com.android.systemui.plugins.clocks.AlarmData
import com.android.systemui.plugins.clocks.ClockAnimations
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
@@ -97,7 +98,12 @@ class DefaultClockController(
events.onLocaleChanged(Locale.getDefault())
}
- override fun initialize(isDarkTheme: Boolean, dozeFraction: Float, foldFraction: Float) {
+ override fun initialize(
+ isDarkTheme: Boolean,
+ dozeFraction: Float,
+ foldFraction: Float,
+ clockListener: ClockEventListener?,
+ ) {
largeClock.recomputePadding(null)
largeClock.animations = LargeClockAnimations(largeClock.view, dozeFraction, foldFraction)
@@ -143,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
}
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 654478af3fb0..c3935e68ca04 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
@@ -61,7 +61,14 @@ class DefaultClockProvider(
override fun getClocks(): List<ClockMetadata> {
var clocks = listOf(ClockMetadata(DEFAULT_CLOCK_ID))
- if (isClockReactiveVariantsEnabled) clocks += ClockMetadata(FLEX_CLOCK_ID)
+ if (isClockReactiveVariantsEnabled) {
+ clocks +=
+ ClockMetadata(
+ FLEX_CLOCK_ID,
+ isDeprecated = true,
+ replacementTarget = DEFAULT_CLOCK_ID,
+ )
+ }
return clocks
}
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 1a1033ba42e0..5acd4468fe92 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
@@ -22,6 +22,7 @@ import com.android.systemui.plugins.clocks.AlarmData
import com.android.systemui.plugins.clocks.AxisType
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
@@ -102,9 +103,15 @@ class FlexClockController(private val clockCtx: ClockContext) : ClockController
}
}
- override fun initialize(isDarkTheme: Boolean, dozeFraction: Float, foldFraction: Float) {
+ override fun initialize(
+ isDarkTheme: Boolean,
+ dozeFraction: Float,
+ foldFraction: Float,
+ clockListener: ClockEventListener?,
+ ) {
events.onFontAxesChanged(clockCtx.settings.axes)
smallClock.run {
+ layerController.onViewBoundsChanged = { clockListener?.onBoundsChanged(it) }
events.onThemeChanged(theme.copy(isDarkTheme = isDarkTheme))
animations.doze(dozeFraction)
animations.fold(foldFraction)
@@ -112,6 +119,7 @@ class FlexClockController(private val clockCtx: ClockContext) : ClockController
}
largeClock.run {
+ layerController.onViewBoundsChanged = { clockListener?.onBoundsChanged(it) }
events.onThemeChanged(theme.copy(isDarkTheme = isDarkTheme))
animations.doze(dozeFraction)
animations.fold(foldFraction)
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 2282863b4017..578a489c68c6 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
@@ -247,7 +247,7 @@ class FlexClockFaceController(clockCtx: ClockContext, private val isLargeClock:
timespec = DigitalTimespec.TIME_FULL_FORMAT,
style = FontTextStyle(fontSizeScale = 0.98f),
aodStyle = FontTextStyle(),
- alignment = DigitalAlignment(HorizontalAlignment.LEFT, VerticalAlignment.CENTER),
+ alignment = DigitalAlignment(HorizontalAlignment.START, VerticalAlignment.CENTER),
dateTimeFormat = "h:mm",
)
}
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 af00cc264208..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
@@ -22,6 +22,7 @@ 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
@@ -31,4 +32,5 @@ interface SimpleClockLayerController {
val config: ClockFaceConfig
@VisibleForTesting var fakeTimeMills: Long?
+ 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 0b7ea1a335ef..1d963af3ad22 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
@@ -85,6 +85,7 @@ open class SimpleDigitalHandLayerController(
override val view = SimpleDigitalClockTextView(clockCtx, isLargeClock)
private val logger = Logger(clockCtx.messageBuffer, TAG)
val timespec = DigitalTimespecHandler(layerCfg.timespec, layerCfg.dateTimeFormat)
+ override var onViewBoundsChanged by view::onViewBoundsChanged
@VisibleForTesting
override var fakeTimeMills: Long?
@@ -228,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 2d0ca5331299..ba32ab083063 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
@@ -29,13 +28,15 @@ 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.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,6 +101,7 @@ class FlexClockView(clockCtx: ClockContext) : ViewGroup(clockCtx.context) {
updateLocale(Locale.getDefault())
}
+ var onViewBoundsChanged: ((VRectF) -> Unit)? = null
private val digitOffsets = mutableMapOf<Int, Float>()
protected fun calculateSize(
@@ -108,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
@@ -179,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)
@@ -189,13 +189,15 @@ class FlexClockView(clockCtx: ClockContext) : ViewGroup(clockCtx.context) {
fun updateLocation() {
val layoutBounds = this.layoutBounds ?: return
+ val bounds = VRectF.fromCenter(layoutBounds.center, this.measuredSize)
setFrame(
- (layoutBounds.centerX() - measuredWidth / 2f).roundToInt(),
- (layoutBounds.centerY() - measuredHeight / 2f).roundToInt(),
- (layoutBounds.centerX() + measuredWidth / 2f).roundToInt(),
- (layoutBounds.centerY() + measuredHeight / 2f).roundToInt(),
+ bounds.left.roundToInt(),
+ bounds.top.roundToInt(),
+ bounds.right.roundToInt(),
+ bounds.bottom.roundToInt(),
)
updateChildFrames(isLayout = false)
+ onViewBoundsChanged?.let { it(bounds) }
}
override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
@@ -207,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)
}
@@ -348,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) {
@@ -432,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 015a82707ea2..2af25fe339a2 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,7 +23,6 @@ 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
@@ -31,6 +30,7 @@ import android.util.AttributeSet
import android.util.Log
import android.util.MathUtils.lerp
import android.util.TypedValue
+import android.view.View
import android.view.View.MeasureSpec.EXACTLY
import android.view.animation.Interpolator
import android.view.animation.PathInterpolator
@@ -44,6 +44,11 @@ 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.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
@@ -51,9 +56,6 @@ 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.ViewUtils.measuredSize
import com.android.systemui.shared.clocks.ViewUtils.size
import com.android.systemui.shared.clocks.toClockAxisSetting
@@ -64,24 +66,48 @@ 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 {
TOP,
BOTTOM,
- BASELINE, // default
+ BASELINE,
CENTER,
}
enum class HorizontalAlignment {
+ LEFT {
+ override fun resolveXAlignment(view: View) = XAlignment.LEFT
+ },
+ RIGHT {
+ override fun resolveXAlignment(view: View) = XAlignment.RIGHT
+ },
+ START {
+ override fun resolveXAlignment(view: View): XAlignment {
+ return if (view.isLayoutRtl()) XAlignment.RIGHT else XAlignment.LEFT
+ }
+ },
+ END {
+ override fun resolveXAlignment(view: View): XAlignment {
+ return if (view.isLayoutRtl()) XAlignment.LEFT else XAlignment.RIGHT
+ }
+ },
+ CENTER {
+ override fun resolveXAlignment(view: View) = XAlignment.CENTER
+ };
+
+ abstract fun resolveXAlignment(view: View): XAlignment
+}
+
+enum class XAlignment {
LEFT,
RIGHT,
- CENTER, // default
+ CENTER,
}
@SuppressLint("AppCompatCustomView")
@@ -117,6 +143,7 @@ open class SimpleDigitalClockTextView(
fidgetFontVariation = buildFidgetVariation(lsFontAxes).toFVar()
}
+ var onViewBoundsChanged: ((VRectF) -> Unit)? = null
private val parser = DimensionParser(clockCtx.context)
var maxSingleDigitHeight = -1f
var maxSingleDigitWidth = -1f
@@ -132,13 +159,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
@@ -154,7 +181,11 @@ open class SimpleDigitalClockTextView(
}
var verticalAlignment: VerticalAlignment = VerticalAlignment.BASELINE
- var horizontalAlignment: HorizontalAlignment = HorizontalAlignment.LEFT
+ var horizontalAlignment: HorizontalAlignment = HorizontalAlignment.CENTER
+
+ val xAlignment: XAlignment
+ get() = horizontalAlignment.resolveXAlignment(this)
+
var isAnimationEnabled = true
var dozeFraction: Float = 0f
set(value) {
@@ -184,8 +215,8 @@ open class SimpleDigitalClockTextView(
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)
@@ -256,6 +287,7 @@ open class SimpleDigitalClockTextView(
canvas.use {
digitTranslateAnimator?.apply { canvas.translate(currentTranslation) }
canvas.translate(getDrawTranslation(interpBounds))
+ if (isLayoutRtl()) canvas.translate(interpBounds.width - textBounds.width, 0f)
textAnimator.draw(canvas)
}
}
@@ -270,16 +302,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() {
@@ -295,11 +323,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(
@@ -309,6 +337,11 @@ open class SimpleDigitalClockTextView(
),
)
updateTextBoundsForTextAnimator()
+
+ if (!isAnimated) {
+ requestLayout()
+ (parent as? FlexClockView)?.requestLayout()
+ }
}
fun animateCharge() {
@@ -341,7 +374,9 @@ open class SimpleDigitalClockTextView(
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
@@ -350,13 +385,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),
@@ -374,10 +409,10 @@ open class SimpleDigitalClockTextView(
}
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()
@@ -398,21 +433,23 @@ open class SimpleDigitalClockTextView(
}
/** 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 = getInterpolatedProgress()): 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 {
@@ -425,11 +462,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
},
)
}
@@ -453,61 +490,41 @@ open class SimpleDigitalClockTextView(
}
/** Set the location of the view to match the interpolated text bounds */
- private fun setInterpolatedLocation(measureSize: VPointF): RectF {
- val targetRect = RectF()
- targetRect.apply {
- when (horizontalAlignment) {
- HorizontalAlignment.LEFT -> {
- left = layoutBounds.left
- right = layoutBounds.left + measureSize.x
- }
- HorizontalAlignment.CENTER -> {
- left = layoutBounds.centerX() - measureSize.x / 2f
- right = layoutBounds.centerX() + measureSize.x / 2f
- }
- HorizontalAlignment.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(),
targetRect.right.roundToInt(),
targetRect.bottom.roundToInt(),
)
+ onViewBoundsChanged?.let { it(targetRect) }
return targetRect
}
- private fun getDrawTranslation(interpBounds: RectF): VPointF {
+ private fun getDrawTranslation(interpBounds: VRectF): VPointF {
val sizeDiff = this.measuredSize - interpBounds.size
val alignment =
VPointF(
- when (horizontalAlignment) {
- HorizontalAlignment.LEFT -> 0f
- HorizontalAlignment.CENTER -> 0.5f
- HorizontalAlignment.RIGHT -> 1f
+ when (xAlignment) {
+ XAlignment.LEFT -> 0f
+ XAlignment.CENTER -> 0.5f
+ XAlignment.RIGHT -> 1f
},
when (verticalAlignment) {
VerticalAlignment.TOP -> 0f
@@ -549,11 +566,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
}
@@ -571,8 +588,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
@@ -600,8 +617,8 @@ open class SimpleDigitalClockTextView(
* and targetPaint will store the state we transition to
*/
private fun updateTextBoundsForTextAnimator() {
- textAnimator.textInterpolator.basePaint.getTextBounds(text, prevTextBounds)
- textAnimator.textInterpolator.targetPaint.getTextBounds(text, targetTextBounds)
+ prevTextBounds = textAnimator.textInterpolator.basePaint.getTextBounds(text)
+ targetTextBounds = textAnimator.textInterpolator.targetPaint.getTextBounds(text)
}
/**
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/KeyguardSecurityContainerControllerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.kt
index 189d554415d0..f4d4b1efa3e2 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.kt
@@ -25,6 +25,8 @@ import android.view.Gravity
import android.view.LayoutInflater
import android.view.MotionEvent
import android.view.View
+import android.view.ViewTreeObserver
+import android.view.ViewTreeObserver.OnPreDrawListener
import android.view.WindowInsetsController
import android.widget.FrameLayout
import androidx.test.ext.junit.runners.AndroidJUnit4
@@ -720,6 +722,37 @@ class KeyguardSecurityContainerControllerTest : SysuiTestCase() {
}
@Test
+ fun startAppearAnimation_ifDelayed() {
+ val argumentCaptor = ArgumentCaptor.forClass(OnPreDrawListener::class.java)
+ whenever(view.isAppearAnimationDelayed).thenReturn(true)
+ val viewTreeObserver: ViewTreeObserver = mock()
+ whenever(view.viewTreeObserver).thenReturn(viewTreeObserver)
+
+ underTest.startAppearAnimationIfDelayed()
+
+ verify(view).alpha = 1f
+ verify(viewTreeObserver).addOnPreDrawListener(argumentCaptor.capture())
+ argumentCaptor.value.onPreDraw()
+
+ verify(view).startAppearAnimation(any(SecurityMode::class.java))
+ verify(view).setIsAppearAnimationDelayed(false)
+ }
+
+ @Test
+ fun appearAnimation_willNotStart_ifNotDelayed() {
+ whenever(view.isAppearAnimationDelayed).thenReturn(false)
+ val viewTreeObserver: ViewTreeObserver = mock()
+ whenever(view.viewTreeObserver).thenReturn(viewTreeObserver)
+
+ underTest.startAppearAnimationIfDelayed()
+
+ verify(view, never()).alpha
+ verify(viewTreeObserver, never()).addOnPreDrawListener(any())
+
+ verify(view, never()).startAppearAnimation(any(SecurityMode::class.java))
+ }
+
+ @Test
fun gravityReappliedOnConfigurationChange() {
// Set initial gravity
testableResources.addOverride(R.integer.keyguard_host_view_gravity, Gravity.CENTER)
diff --git a/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardSecurityContainerTest.java b/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardSecurityContainerTest.java
index 176824fd4c5d..2845f6a2983a 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardSecurityContainerTest.java
+++ b/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardSecurityContainerTest.java
@@ -452,6 +452,14 @@ public class KeyguardSecurityContainerTest extends SysuiTestCase {
verify(keyguardPasswordView).setDisappearAnimationListener(any());
}
+ @Test
+ public void setupForDelayedAppear() {
+ mKeyguardSecurityContainer.setupForDelayedAppear();
+ assertThat(mKeyguardSecurityContainer.getTranslationY()).isEqualTo(0f);
+ assertThat(mKeyguardSecurityContainer.getAlpha()).isEqualTo(0f);
+ assertThat(mKeyguardSecurityContainer.isAppearAnimationDelayed()).isTrue();
+ }
+
private BackEvent createBackEvent(float touchX, float progress) {
return new BackEvent(0, 0, progress, BackEvent.EDGE_LEFT);
}
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..7e4704a6179a 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,8 @@ public class HearingDevicesDialogDelegateTest extends SysuiTestCase {
@Mock
private HearingDevicesUiEventLogger mUiEventLogger;
@Mock
+ private QSSettingsPackageRepository mQSSettingsPackageRepository;
+ @Mock
private CachedBluetoothDevice mCachedDevice;
@Mock
private BluetoothDevice mDevice;
@@ -164,6 +168,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);
@@ -195,6 +201,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 +217,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);
}
@@ -392,7 +400,8 @@ public class HearingDevicesDialogDelegateTest extends SysuiTestCase {
mExecutor,
mExecutor,
mAudioManager,
- mUiEventLogger
+ mUiEventLogger,
+ mQSSettingsPackageRepository
);
mDialog = mDialogDelegate.createDialog();
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/ambient/touch/BouncerFullscreenSwipeTouchHandlerTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/ambient/touch/BouncerFullscreenSwipeTouchHandlerTest.java
index f53f964cd3d9..191ecccd5f71 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/ambient/touch/BouncerFullscreenSwipeTouchHandlerTest.java
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/ambient/touch/BouncerFullscreenSwipeTouchHandlerTest.java
@@ -28,6 +28,8 @@ import static org.mockito.Mockito.when;
import android.animation.ValueAnimator;
import android.content.pm.UserInfo;
+import android.content.res.Configuration;
+import android.content.res.Resources;
import android.graphics.Rect;
import android.platform.test.annotations.DisableFlags;
import android.platform.test.annotations.EnableFlags;
@@ -54,6 +56,7 @@ import com.android.systemui.scene.ui.view.WindowRootView;
import com.android.systemui.shared.system.InputChannelCompat;
import com.android.systemui.statusbar.NotificationShadeWindowController;
import com.android.systemui.statusbar.phone.CentralSurfaces;
+import com.android.systemui.statusbar.policy.KeyguardStateController;
import com.android.wm.shell.animation.FlingAnimationUtils;
import org.junit.Before;
@@ -127,10 +130,16 @@ public class BouncerFullscreenSwipeTouchHandlerTest extends SysuiTestCase {
@Mock
WindowRootView mWindowRootView;
+ @Mock
+ Resources mResources;
+
private SceneInteractor mSceneInteractor;
+ private KeyguardStateController mKeyguardStateController;
+
private static final float TOUCH_REGION = .3f;
private static final float MIN_BOUNCER_HEIGHT = .05f;
+ private final Configuration mConfiguration = new Configuration();
private static final Rect SCREEN_BOUNDS = new Rect(0, 0, 1024, 100);
private static final UserInfo CURRENT_USER_INFO = new UserInfo(
@@ -153,6 +162,8 @@ public class BouncerFullscreenSwipeTouchHandlerTest extends SysuiTestCase {
public void setup() {
mKosmos = new KosmosJavaAdapter(this);
mSceneInteractor = spy(mKosmos.getSceneInteractor());
+ mKeyguardStateController = mKosmos.getKeyguardStateController();
+ mConfiguration.orientation = Configuration.ORIENTATION_PORTRAIT;
MockitoAnnotations.initMocks(this);
mTouchHandler = new BouncerSwipeTouchHandler(
@@ -172,7 +183,9 @@ public class BouncerFullscreenSwipeTouchHandlerTest extends SysuiTestCase {
mKeyguardInteractor,
mSceneInteractor,
mKosmos.getShadeRepository(),
- Optional.of(() -> mWindowRootView));
+ Optional.of(() -> mWindowRootView),
+ mKeyguardStateController,
+ mKosmos.getCommunalSettingsInteractor());
when(mScrimManager.getCurrentController()).thenReturn(mScrimController);
when(mValueAnimatorCreator.create(anyFloat(), anyFloat())).thenReturn(mValueAnimator);
@@ -180,6 +193,9 @@ public class BouncerFullscreenSwipeTouchHandlerTest extends SysuiTestCase {
when(mFlingAnimationUtils.getMinVelocityPxPerSecond()).thenReturn(Float.MAX_VALUE);
when(mTouchSession.getBounds()).thenReturn(SCREEN_BOUNDS);
when(mKeyguardInteractor.isKeyguardDismissible()).thenReturn(MutableStateFlow(false));
+ when(mKeyguardStateController.isKeyguardScreenRotationAllowed()).thenReturn(true);
+ when(mWindowRootView.getResources()).thenReturn(mResources);
+ when(mResources.getConfiguration()).thenReturn(mConfiguration);
}
/**
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/ambient/touch/BouncerSwipeTouchHandlerTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/ambient/touch/BouncerSwipeTouchHandlerTest.java
index dd43d817cccc..e8dc6762cc92 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/ambient/touch/BouncerSwipeTouchHandlerTest.java
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/ambient/touch/BouncerSwipeTouchHandlerTest.java
@@ -16,6 +16,10 @@
package com.android.systemui.ambient.touch;
+import static android.platform.test.flag.junit.FlagsParameterization.allCombinationsOf;
+
+import static com.android.systemui.Flags.FLAG_GLANCEABLE_HUB_V2;
+
import static com.google.common.truth.Truth.assertThat;
import static kotlinx.coroutines.flow.StateFlowKt.MutableStateFlow;
@@ -34,6 +38,8 @@ import static org.mockito.Mockito.when;
import android.animation.AnimatorListenerAdapter;
import android.animation.ValueAnimator;
import android.content.pm.UserInfo;
+import android.content.res.Configuration;
+import android.content.res.Resources;
import android.graphics.Rect;
import android.graphics.Region;
import android.platform.test.annotations.DisableFlags;
@@ -63,6 +69,7 @@ import com.android.systemui.shade.ShadeExpansionChangeEvent;
import com.android.systemui.shared.system.InputChannelCompat;
import com.android.systemui.statusbar.NotificationShadeWindowController;
import com.android.systemui.statusbar.phone.CentralSurfaces;
+import com.android.systemui.statusbar.policy.KeyguardStateController;
import com.android.wm.shell.animation.FlingAnimationUtils;
import org.junit.Before;
@@ -132,12 +139,16 @@ public class BouncerSwipeTouchHandlerTest extends SysuiTestCase {
@Mock
WindowRootView mWindowRootView;
+ Resources mResources;
+
@Mock
CommunalViewModel mCommunalViewModel;
@Mock
KeyguardInteractor mKeyguardInteractor;
+ private KeyguardStateController mKeyguardStateController;
+
@Captor
ArgumentCaptor<Rect> mRectCaptor;
@@ -147,6 +158,7 @@ public class BouncerSwipeTouchHandlerTest extends SysuiTestCase {
private static final int SCREEN_WIDTH_PX = 1024;
private static final int SCREEN_HEIGHT_PX = 100;
private static final float MIN_BOUNCER_HEIGHT = .05f;
+ private final Configuration mConfiguration = new Configuration();
private static final Rect SCREEN_BOUNDS = new Rect(0, 0, 1024, 100);
private static final UserInfo CURRENT_USER_INFO = new UserInfo(
@@ -157,7 +169,8 @@ public class BouncerSwipeTouchHandlerTest extends SysuiTestCase {
@Parameters(name = "{0}")
public static List<FlagsParameterization> getParams() {
- return SceneContainerFlagParameterizationKt.parameterizeSceneContainerFlag();
+ return SceneContainerFlagParameterizationKt
+ .andSceneContainer(allCombinationsOf(Flags.FLAG_GLANCEABLE_HUB_V2));
}
public BouncerSwipeTouchHandlerTest(FlagsParameterization flags) {
@@ -168,7 +181,13 @@ public class BouncerSwipeTouchHandlerTest extends SysuiTestCase {
@Before
public void setup() {
mKosmos = new KosmosJavaAdapter(this);
+ mContext.ensureTestableResources();
+ mResources = mContext.getResources();
+ overrideConfiguration(mConfiguration);
+ mConfiguration.orientation = Configuration.ORIENTATION_PORTRAIT;
+
mSceneInteractor = spy(mKosmos.getSceneInteractor());
+ mKeyguardStateController = mKosmos.getKeyguardStateController();
MockitoAnnotations.initMocks(this);
mTouchHandler = new BouncerSwipeTouchHandler(
@@ -188,7 +207,9 @@ public class BouncerSwipeTouchHandlerTest extends SysuiTestCase {
mKeyguardInteractor,
mSceneInteractor,
mKosmos.getShadeRepository(),
- Optional.of(() -> mWindowRootView)
+ Optional.of(() -> mWindowRootView),
+ mKeyguardStateController,
+ mKosmos.getCommunalSettingsInteractor()
);
when(mScrimManager.getCurrentController()).thenReturn(mScrimController);
@@ -197,6 +218,9 @@ public class BouncerSwipeTouchHandlerTest extends SysuiTestCase {
when(mFlingAnimationUtils.getMinVelocityPxPerSecond()).thenReturn(Float.MAX_VALUE);
when(mTouchSession.getBounds()).thenReturn(SCREEN_BOUNDS);
when(mKeyguardInteractor.isKeyguardDismissible()).thenReturn(MutableStateFlow(false));
+ when(mKeyguardStateController.isKeyguardScreenRotationAllowed()).thenReturn(true);
+ when(mWindowRootView.getResources()).thenReturn(mResources);
+ setCommunalV2ConfigEnabled(true);
}
/**
@@ -586,6 +610,43 @@ public class BouncerSwipeTouchHandlerTest extends SysuiTestCase {
verify(mUiEventLogger).log(BouncerSwipeTouchHandler.DreamEvent.DREAM_BOUNCER_FULLY_VISIBLE);
}
+ @Test
+ @DisableFlags(Flags.FLAG_SCENE_CONTAINER)
+ @EnableFlags(FLAG_GLANCEABLE_HUB_V2)
+ public void swipeUpAboveThresholdInLandscape_keyguardRotationNotAllowed_showsBouncer() {
+ when(mKeyguardStateController.isKeyguardScreenRotationAllowed()).thenReturn(false);
+ mConfiguration.orientation = Configuration.ORIENTATION_LANDSCAPE;
+
+ final float swipeUpPercentage = .1f;
+ // The upward velocity is ignored.
+ final float velocityY = -1;
+ swipeToPosition(swipeUpPercentage, velocityY);
+
+ // Ensure show bouncer scrimmed
+ verify(mScrimController).show(true);
+ verify(mValueAnimatorCreator, never()).create(anyFloat(), anyFloat());
+ verify(mValueAnimator, never()).start();
+ }
+
+ @Test
+ @DisableFlags(Flags.FLAG_SCENE_CONTAINER)
+ @EnableFlags(FLAG_GLANCEABLE_HUB_V2)
+ public void swipeUpBelowThreshold_inLandscapeKeyguardRotationNotAllowed_noBouncer() {
+ mConfiguration.orientation = Configuration.ORIENTATION_LANDSCAPE;
+
+ final float swipeUpPercentage = .02f;
+ // The upward velocity is ignored.
+ final float velocityY = -1;
+ swipeToPosition(swipeUpPercentage, velocityY);
+
+ // no bouncer shown scrimmed
+ verify(mScrimController, never()).show(true);
+ // on touch end, bouncer hidden
+ verify(mValueAnimatorCreator).create(eq(1 - swipeUpPercentage),
+ eq(KeyguardBouncerConstants.EXPANSION_HIDDEN));
+ verify(mValueAnimator, never()).addListener(any());
+ }
+
/**
* Tests that swiping up with a speed above the set threshold will continue the expansion.
*/
@@ -672,4 +733,15 @@ public class BouncerSwipeTouchHandlerTest extends SysuiTestCase {
inputEventListenerCaptor.getValue().onInputEvent(upEvent);
}
+
+ private void setCommunalV2ConfigEnabled(boolean enabled) {
+ mContext.getOrCreateTestableResources().addOverride(
+ com.android.internal.R.bool.config_glanceableHubEnabled,
+ enabled);
+ }
+
+ private void overrideConfiguration(Configuration configuration) {
+ mContext.getOrCreateTestableResources().overrideConfiguration(
+ configuration);
+ }
}
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 4d027088ca1a..5249bbe2a861 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
@@ -26,7 +26,10 @@ import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.airbnb.lottie.LottieAnimationView
import com.android.systemui.SysuiTestCase
+import com.android.systemui.biometrics.data.repository.biometricStatusRepository
import com.android.systemui.biometrics.data.repository.fingerprintPropertyRepository
+import com.android.systemui.biometrics.shared.model.AuthenticationReason
+import com.android.systemui.biometrics.shared.model.AuthenticationReason.SettingsOperations
import com.android.systemui.biometrics.shared.model.DisplayRotation
import com.android.systemui.biometrics.shared.model.FingerprintSensorType
import com.android.systemui.biometrics.shared.model.SensorStrength
@@ -50,7 +53,6 @@ import org.mockito.Captor
import org.mockito.Mock
import org.mockito.Mockito.any
import org.mockito.Mockito.inOrder
-import org.mockito.Mockito.mock
import org.mockito.Mockito.never
import org.mockito.Mockito.verify
import org.mockito.Mockito.`when`
@@ -67,6 +69,7 @@ class SideFpsOverlayViewBinderTest : SysuiTestCase() {
@JvmField @Rule var mockitoRule: MockitoRule = MockitoJUnit.rule()
@Mock private lateinit var layoutInflater: LayoutInflater
@Mock private lateinit var sideFpsView: View
+ @Mock private lateinit var lottieAnimationView: LottieAnimationView
@Captor private lateinit var viewCaptor: ArgumentCaptor<View>
@Before
@@ -76,7 +79,7 @@ class SideFpsOverlayViewBinderTest : SysuiTestCase() {
context.addMockSystemService(WindowManager::class.java, kosmos.windowManager)
`when`(layoutInflater.inflate(R.layout.sidefps_view, null, false)).thenReturn(sideFpsView)
`when`(sideFpsView.requireViewById<LottieAnimationView>(eq(R.id.sidefps_animation)))
- .thenReturn(mock(LottieAnimationView::class.java))
+ .thenReturn(lottieAnimationView)
}
@Test
@@ -184,6 +187,20 @@ class SideFpsOverlayViewBinderTest : SysuiTestCase() {
}
}
+ @Test
+ fun verifyToggleAnimation_onSideFpsIndicatorViewClickedWhileEnrolling() {
+ kosmos.testScope.runTest {
+ kosmos.biometricStatusRepository.setFingerprintAuthenticationReason(
+ AuthenticationReason.SettingsAuthentication(SettingsOperations.ENROLL_ENROLLING)
+ )
+ setupTestConfiguration(isInRearDisplayMode = false)
+ val clickListenerCaptor = ArgumentCaptor.forClass(View.OnClickListener::class.java)
+ verify(sideFpsView).setOnClickListener(clickListenerCaptor.capture())
+ clickListenerCaptor.value.onClick(sideFpsView)
+ verify(lottieAnimationView).toggleAnimation()
+ }
+ }
+
private suspend fun TestScope.setupTestConfiguration(isInRearDisplayMode: Boolean) {
kosmos.fingerprintPropertyRepository.setProperties(
sensorId = 1,
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/bluetooth/qsdialog/AudioSharingInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/bluetooth/qsdialog/AudioSharingInteractorTest.kt
index 682ad0c5c48b..754011be7622 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/bluetooth/qsdialog/AudioSharingInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/bluetooth/qsdialog/AudioSharingInteractorTest.kt
@@ -125,7 +125,9 @@ class AudioSharingInteractorTest : SysuiTestCase() {
bluetoothTileDialogAudioSharingRepository.emitAudioSourceStateUpdate()
runCurrent()
- assertThat(value).isNull()
+ assertThat(value).isEqualTo(Unit)
+ verify(bluetoothTileDialogLogger).logAudioSharingStateChanged(true)
+ verify(bluetoothTileDialogLogger).logAudioSourceStateUpdate()
}
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/bluetooth/qsdialog/AudioSharingRepositoryTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/bluetooth/qsdialog/AudioSharingRepositoryTest.kt
index 587f3cc8357c..159c15b31069 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/bluetooth/qsdialog/AudioSharingRepositoryTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/bluetooth/qsdialog/AudioSharingRepositoryTest.kt
@@ -62,6 +62,7 @@ class AudioSharingRepositoryTest : SysuiTestCase() {
AudioSharingRepositoryImpl(
kosmos.localBluetoothManager,
kosmos.audioSharingRepository,
+ kosmos.bluetoothTileDialogLogger,
kosmos.testDispatcher,
)
}
@@ -95,6 +96,8 @@ class AudioSharingRepositoryTest : SysuiTestCase() {
audioSharingRepository.setAudioSharingAvailable(true)
underTest.startAudioSharing()
verify(leAudioBroadcastProfile).startPrivateBroadcast()
+ verify(bluetoothTileDialogLogger)
+ .logAudioSharingRequest(AudioSharingRequest.START_BROADCAST)
}
}
@@ -105,6 +108,8 @@ class AudioSharingRepositoryTest : SysuiTestCase() {
audioSharingRepository.setAudioSharingAvailable(false)
underTest.startAudioSharing()
verify(leAudioBroadcastProfile, never()).startPrivateBroadcast()
+ verify(bluetoothTileDialogLogger, never())
+ .logAudioSharingRequest(AudioSharingRequest.START_BROADCAST)
}
}
@@ -117,6 +122,8 @@ class AudioSharingRepositoryTest : SysuiTestCase() {
audioSharingRepository.setAudioSharingAvailable(true)
underTest.stopAudioSharing()
verify(leAudioBroadcastProfile).stopLatestBroadcast()
+ verify(bluetoothTileDialogLogger)
+ .logAudioSharingRequest(AudioSharingRequest.STOP_BROADCAST)
}
}
@@ -140,6 +147,7 @@ class AudioSharingRepositoryTest : SysuiTestCase() {
runCurrent()
verify(leAudioBroadcastAssistant, never()).allConnectedDevices
+ verify(bluetoothTileDialogLogger, never()).logAudioSharingRequest(any())
}
}
@@ -157,6 +165,7 @@ class AudioSharingRepositoryTest : SysuiTestCase() {
runCurrent()
verify(leAudioBroadcastAssistant, never()).allConnectedDevices
+ verify(bluetoothTileDialogLogger, never()).logAudioSharingRequest(any())
}
}
@@ -177,6 +186,7 @@ class AudioSharingRepositoryTest : SysuiTestCase() {
runCurrent()
verify(leAudioBroadcastAssistant, never()).addSource(any(), any(), anyBoolean())
+ verify(bluetoothTileDialogLogger, never()).logAudioSharingRequest(any())
}
}
@@ -198,6 +208,8 @@ class AudioSharingRepositoryTest : SysuiTestCase() {
runCurrent()
verify(leAudioBroadcastAssistant).addSource(bluetoothDevice, metadata, false)
+ verify(bluetoothTileDialogLogger)
+ .logAudioSharingRequest(AudioSharingRequest.ADD_SOURCE)
}
}
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/domain/interactor/BouncerInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/domain/interactor/BouncerInteractorTest.kt
index dd4af7bb780e..53ddcfaf9576 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/domain/interactor/BouncerInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/domain/interactor/BouncerInteractorTest.kt
@@ -20,6 +20,7 @@ import android.content.testableContext
import android.provider.Settings.Global.ONE_HANDED_KEYGUARD_SIDE
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
+import com.android.compose.animation.scene.ObservableTransitionState
import com.android.internal.logging.uiEventLoggerFake
import com.android.systemui.SysuiTestCase
import com.android.systemui.authentication.data.repository.FakeAuthenticationRepository
@@ -39,13 +40,22 @@ import com.android.systemui.flags.EnableSceneContainer
import com.android.systemui.flags.Flags.FULL_SCREEN_USER_SWITCHER
import com.android.systemui.flags.fakeFeatureFlagsClassic
import com.android.systemui.keyguard.data.repository.fakeDeviceEntryFaceAuthRepository
+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.power.data.repository.fakePowerRepository
import com.android.systemui.res.R
+import com.android.systemui.scene.data.repository.sceneContainerRepository
+import com.android.systemui.scene.shared.model.Overlays
+import com.android.systemui.scene.shared.model.Scenes
+import com.android.systemui.scene.transitionState
import com.android.systemui.testKosmos
import com.android.systemui.util.settings.fakeGlobalSettings
import com.google.common.truth.Truth.assertThat
import kotlin.time.Duration.Companion.seconds
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.flowOf
import kotlinx.coroutines.test.advanceTimeBy
import kotlinx.coroutines.test.runCurrent
import kotlinx.coroutines.test.runTest
@@ -368,6 +378,76 @@ class BouncerInteractorTest : SysuiTestCase() {
testableResources.removeOverride(R.bool.can_use_one_handed_bouncer)
}
+ @Test
+ fun bouncerExpansion_lockscreenToBouncer() =
+ kosmos.runTest {
+ val bouncerExpansion by collectLastValue(underTest.bouncerExpansion)
+
+ val progress = MutableStateFlow(0f)
+ kosmos.sceneContainerRepository.setTransitionState(transitionState)
+ transitionState.value =
+ ObservableTransitionState.Transition.showOverlay(
+ overlay = Overlays.Bouncer,
+ fromScene = Scenes.Lockscreen,
+ currentOverlays = flowOf(emptySet()),
+ progress = progress,
+ isInitiatedByUserInput = false,
+ isUserInputOngoing = flowOf(false),
+ )
+
+ assertThat(bouncerExpansion).isEqualTo(0f)
+
+ progress.value = 1f
+ assertThat(bouncerExpansion).isEqualTo(1f)
+ }
+
+ @Test
+ fun bouncerExpansion_BouncerToLockscreen() =
+ kosmos.runTest {
+ val bouncerExpansion by collectLastValue(underTest.bouncerExpansion)
+
+ val progress = MutableStateFlow(0f)
+ kosmos.sceneContainerRepository.setTransitionState(transitionState)
+ transitionState.value =
+ ObservableTransitionState.Transition.hideOverlay(
+ overlay = Overlays.Bouncer,
+ toScene = Scenes.Lockscreen,
+ currentOverlays = flowOf(emptySet()),
+ progress = progress,
+ isInitiatedByUserInput = false,
+ isUserInputOngoing = flowOf(false),
+ )
+
+ assertThat(bouncerExpansion).isEqualTo(1f)
+
+ progress.value = 1f
+ assertThat(bouncerExpansion).isEqualTo(0f)
+ }
+
+ @Test
+ fun bouncerExpansion_shadeToLockscreenUnderBouncer() =
+ kosmos.runTest {
+ val bouncerExpansion by collectLastValue(underTest.bouncerExpansion)
+
+ val progress = MutableStateFlow(0f)
+ kosmos.sceneContainerRepository.setTransitionState(transitionState)
+ transitionState.value =
+ ObservableTransitionState.Transition(
+ fromScene = Scenes.Shade,
+ toScene = Scenes.Lockscreen,
+ currentScene = flowOf(Scenes.Lockscreen),
+ progress = progress,
+ isInitiatedByUserInput = false,
+ isUserInputOngoing = flowOf(false),
+ currentOverlays = setOf(Overlays.Bouncer),
+ )
+
+ assertThat(bouncerExpansion).isEqualTo(1f)
+
+ progress.value = 1f
+ assertThat(bouncerExpansion).isEqualTo(1f)
+ }
+
companion object {
private const val MESSAGE_ENTER_YOUR_PIN = "Enter your PIN"
private const val MESSAGE_ENTER_YOUR_PASSWORD = "Enter your password"
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..239e02640908
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/clipboardoverlay/ActionIntentCreatorTest.kt
@@ -0,0 +1,150 @@
+/*
+ * 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 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() {
+ val creator = ActionIntentCreator()
+
+ @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() {
+ 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..126b3fa9e7ca 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/clipboardoverlay/IntentCreatorTest.java
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/clipboardoverlay/DefaultIntentCreatorTest.java
@@ -36,13 +36,15 @@ import org.junit.runner.RunWith;
@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 +56,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,7 +68,7 @@ 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());
}
@@ -75,7 +77,7 @@ public class IntentCreatorTest extends SysuiTestCase {
getContext().getOrCreateTestableResources().addOverride(R.string.config_screenshotEditor,
"");
Uri fakeUri = Uri.parse("content://foo");
- Intent intent = IntentCreator.getImageEditIntent(fakeUri, getContext());
+ Intent intent = mIntentCreator.getImageEditIntent(fakeUri, getContext());
assertEquals(Intent.ACTION_EDIT, intent.getAction());
assertEquals("image/*", intent.getType());
@@ -88,14 +90,14 @@ public class IntentCreatorTest extends SysuiTestCase {
"com.android.remotecopy.RemoteCopyActivity");
getContext().getOrCreateTestableResources().addOverride(R.string.config_screenshotEditor,
fakeComponent.flattenToString());
- intent = IntentCreator.getImageEditIntent(fakeUri, getContext());
+ intent = mIntentCreator.getImageEditIntent(fakeUri, getContext());
assertEquals(fakeComponent, intent.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 +110,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 +124,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 +137,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/communal/CommunalSceneStartableTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/CommunalSceneStartableTest.kt
index 7051f81cfc88..f58391496e65 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/CommunalSceneStartableTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/CommunalSceneStartableTest.kt
@@ -22,6 +22,7 @@ import android.platform.test.annotations.EnableFlags
import android.platform.test.flag.junit.FlagsParameterization
import android.provider.Settings
import androidx.test.filters.SmallTest
+import com.android.compose.animation.scene.ObservableTransitionState
import com.android.internal.logging.uiEventLoggerFake
import com.android.systemui.Flags.FLAG_COMMUNAL_HUB
import com.android.systemui.Flags.FLAG_GLANCEABLE_HUB_V2
@@ -38,8 +39,13 @@ import com.android.systemui.flags.Flags.COMMUNAL_SERVICE_ENABLED
import com.android.systemui.flags.andSceneContainer
import com.android.systemui.flags.fakeFeatureFlagsClassic
import com.android.systemui.keyguard.data.repository.fakeKeyguardRepository
+import com.android.systemui.keyguard.data.repository.fakeKeyguardTransitionRepository
import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor
import com.android.systemui.keyguard.domain.interactor.keyguardInteractor
+import com.android.systemui.keyguard.domain.interactor.keyguardTransitionInteractor
+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.kosmos.Kosmos
import com.android.systemui.kosmos.applicationCoroutineScope
import com.android.systemui.kosmos.collectLastValue
@@ -56,11 +62,15 @@ import com.google.common.truth.Truth.assertThat
import kotlin.time.Duration
import kotlin.time.Duration.Companion.milliseconds
import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.flowOf
import kotlinx.coroutines.runBlocking
import kotlinx.coroutines.test.advanceTimeBy
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
+import org.mockito.Mockito
+import org.mockito.kotlin.verify
import platform.test.runner.parameterized.ParameterizedAndroidJunit4
import platform.test.runner.parameterized.Parameters
@@ -93,10 +103,12 @@ class CommunalSceneStartableTest(flags: FlagsParameterization) : SysuiTestCase()
communalInteractor = communalInteractor,
communalSettingsInteractor = communalSettingsInteractor,
communalSceneInteractor = communalSceneInteractor,
+ keyguardTransitionInteractor = keyguardTransitionInteractor,
keyguardInteractor = keyguardInteractor,
systemSettings = fakeSettings,
notificationShadeWindowController = notificationShadeWindowController,
bgScope = applicationCoroutineScope,
+ applicationScope = applicationCoroutineScope,
mainDispatcher = testDispatcher,
uiEventLogger = uiEventLoggerFake,
)
@@ -111,13 +123,13 @@ class CommunalSceneStartableTest(flags: FlagsParameterization) : SysuiTestCase()
UserHandle.USER_CURRENT,
)
fakeFeatureFlagsClassic.set(COMMUNAL_SERVICE_ENABLED, true)
+ setCommunalV2ConfigEnabled(true)
underTest.start()
// Make communal available so that communalInteractor.desiredScene accurately reflects
// scene changes instead of just returning Blank.
runBlocking { setCommunalAvailable(true) }
- setCommunalV2ConfigEnabled(true)
}
}
@@ -414,6 +426,107 @@ class CommunalSceneStartableTest(flags: FlagsParameterization) : SysuiTestCase()
assertThat(uiEventLoggerFake.numLogs()).isEqualTo(1)
}
+ @Test
+ @DisableFlags(FLAG_SCENE_CONTAINER)
+ @EnableFlags(FLAG_GLANCEABLE_HUB_V2)
+ fun glanceableHubOrientationAware_idleOnCommunal() =
+ kosmos.runTest {
+ communalSceneInteractor.changeScene(CommunalScenes.Communal, "test")
+
+ val scene by collectLastValue(communalSceneInteractor.currentScene)
+ assertThat(scene).isEqualTo(CommunalScenes.Communal)
+
+ verify(notificationShadeWindowController).setGlanceableHubOrientationAware(true)
+ }
+
+ @Test
+ @DisableFlags(FLAG_SCENE_CONTAINER)
+ @EnableFlags(FLAG_GLANCEABLE_HUB_V2)
+ fun glanceableHubOrientationAware_transitioningToCommunal() =
+ kosmos.runTest {
+ val progress = MutableStateFlow(0f)
+ val targetScene = CommunalScenes.Communal
+ val currentScene = CommunalScenes.Blank
+ val transitionState =
+ MutableStateFlow(
+ ObservableTransitionState.Transition(
+ fromScene = currentScene,
+ toScene = targetScene,
+ currentScene = flowOf(targetScene),
+ progress = progress,
+ isInitiatedByUserInput = false,
+ isUserInputOngoing = flowOf(false),
+ )
+ )
+ communalSceneInteractor.setTransitionState(transitionState)
+
+ // Partially transition.
+ progress.value = .4f
+
+ val scene by collectLastValue(communalSceneInteractor.currentScene)
+ assertThat(scene).isEqualTo(CommunalScenes.Blank)
+
+ verify(notificationShadeWindowController).setGlanceableHubOrientationAware(true)
+ }
+
+ @Test
+ @DisableFlags(FLAG_SCENE_CONTAINER)
+ @EnableFlags(FLAG_GLANCEABLE_HUB_V2)
+ fun glanceableHubOrientationAware_communalToDreaming() =
+ kosmos.runTest {
+ communalSceneInteractor.changeScene(CommunalScenes.Communal, "test")
+
+ verify(notificationShadeWindowController).setGlanceableHubOrientationAware(true)
+ Mockito.clearInvocations(notificationShadeWindowController)
+
+ val progress = MutableStateFlow(0f)
+ val currentScene = CommunalScenes.Communal
+ val targetScene = CommunalScenes.Blank
+ val transitionState =
+ MutableStateFlow(
+ ObservableTransitionState.Transition(
+ fromScene = currentScene,
+ toScene = targetScene,
+ currentScene = flowOf(targetScene),
+ progress = progress,
+ isInitiatedByUserInput = false,
+ isUserInputOngoing = flowOf(false),
+ )
+ )
+ communalSceneInteractor.setTransitionState(transitionState)
+
+ // Partially transitioned out of Communal scene
+ progress.value = .4f
+
+ // Started keyguard transitioning from hub -> dreaming.
+ fakeKeyguardTransitionRepository.sendTransitionStep(
+ TransitionStep(
+ from = KeyguardState.GLANCEABLE_HUB,
+ to = KeyguardState.DREAMING,
+ transitionState = TransitionState.STARTED,
+ )
+ )
+ verify(notificationShadeWindowController).setGlanceableHubOrientationAware(true)
+ Mockito.clearInvocations(notificationShadeWindowController)
+
+ fakeKeyguardTransitionRepository.sendTransitionStep(
+ from = KeyguardState.GLANCEABLE_HUB,
+ to = KeyguardState.DREAMING,
+ transitionState = TransitionState.RUNNING,
+ value = 0.5f,
+ )
+
+ // Transitioned to dreaming.
+ fakeKeyguardTransitionRepository.sendTransitionStep(
+ from = KeyguardState.GLANCEABLE_HUB,
+ to = KeyguardState.DREAMING,
+ transitionState = TransitionState.FINISHED,
+ value = 1f,
+ )
+ // Not on hub anymore, let other states take control
+ verify(notificationShadeWindowController).setGlanceableHubOrientationAware(false)
+ }
+
/**
* Advances time by duration + 1 millisecond, to ensure that tasks scheduled to run at
* currentTime + duration are scheduled.
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/CommunalSceneInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/CommunalSceneInteractorTest.kt
index 77d7091e463a..dc21f0692c9e 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/CommunalSceneInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/CommunalSceneInteractorTest.kt
@@ -134,7 +134,7 @@ class CommunalSceneInteractorTest(flags: FlagsParameterization) : SysuiTestCase(
underTest.snapToScene(
CommunalScenes.Communal,
"test",
- ActivityTransitionAnimator.TIMINGS.totalDuration
+ ActivityTransitionAnimator.TIMINGS.totalDuration,
)
assertThat(currentScene).isEqualTo(CommunalScenes.Blank)
advanceTimeBy(ActivityTransitionAnimator.TIMINGS.totalDuration)
@@ -269,6 +269,48 @@ class CommunalSceneInteractorTest(flags: FlagsParameterization) : SysuiTestCase(
@DisableFlags(FLAG_SCENE_CONTAINER)
@Test
+ fun isTransitioningToOrIdleOnCommunal() =
+ testScope.runTest {
+ // isIdleOnCommunal is false when not on communal.
+ val isTransitioningToOrIdleOnCommunal by
+ collectLastValue(underTest.isTransitioningToOrIdleOnCommunal)
+ assertThat(isTransitioningToOrIdleOnCommunal).isEqualTo(false)
+
+ val transitionState: MutableStateFlow<ObservableTransitionState> =
+ MutableStateFlow(
+ ObservableTransitionState.Transition(
+ fromScene = CommunalScenes.Blank,
+ toScene = CommunalScenes.Communal,
+ currentScene = flowOf(CommunalScenes.Communal),
+ progress = flowOf(0f),
+ isInitiatedByUserInput = false,
+ isUserInputOngoing = flowOf(false),
+ )
+ )
+
+ // Start transition to communal.
+ repository.setTransitionState(transitionState)
+ assertThat(isTransitioningToOrIdleOnCommunal).isEqualTo(true)
+
+ // Finish transition to communal
+ transitionState.value = ObservableTransitionState.Idle(CommunalScenes.Communal)
+ assertThat(isTransitioningToOrIdleOnCommunal).isEqualTo(true)
+
+ // Start transition away from communal.
+ transitionState.value =
+ ObservableTransitionState.Transition(
+ fromScene = CommunalScenes.Communal,
+ toScene = CommunalScenes.Blank,
+ currentScene = flowOf(CommunalScenes.Blank),
+ progress = flowOf(.1f),
+ isInitiatedByUserInput = false,
+ isUserInputOngoing = flowOf(false),
+ )
+ assertThat(isTransitioningToOrIdleOnCommunal).isEqualTo(false)
+ }
+
+ @DisableFlags(FLAG_SCENE_CONTAINER)
+ @Test
fun isCommunalVisible() =
testScope.runTest {
// isCommunalVisible is false when not on communal.
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/ui/viewmodel/CommunalToDreamButtonViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/ui/viewmodel/CommunalToDreamButtonViewModelTest.kt
deleted file mode 100644
index 2f3073e8eb23..000000000000
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/ui/viewmodel/CommunalToDreamButtonViewModelTest.kt
+++ /dev/null
@@ -1,173 +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.ui.viewmodel
-
-import android.content.pm.UserInfo
-import android.platform.test.annotations.EnableFlags
-import android.provider.Settings
-import android.service.dream.dreamManager
-import androidx.test.ext.junit.runners.AndroidJUnit4
-import androidx.test.filters.SmallTest
-import com.android.internal.logging.uiEventLoggerFake
-import com.android.systemui.Flags.FLAG_GLANCEABLE_HUB_V2
-import com.android.systemui.SysuiTestCase
-import com.android.systemui.communal.data.repository.fakeCommunalPrefsRepository
-import com.android.systemui.communal.domain.interactor.HubOnboardingInteractorTest.Companion.MAIN_USER
-import com.android.systemui.communal.shared.log.CommunalUiEvent
-import com.android.systemui.flags.Flags.COMMUNAL_SERVICE_ENABLED
-import com.android.systemui.flags.fakeFeatureFlagsClassic
-import com.android.systemui.kosmos.runCurrent
-import com.android.systemui.kosmos.runTest
-import com.android.systemui.kosmos.testScope
-import com.android.systemui.lifecycle.activateIn
-import com.android.systemui.plugins.activityStarter
-import com.android.systemui.settings.fakeUserTracker
-import com.android.systemui.statusbar.policy.batteryController
-import com.android.systemui.statusbar.policy.fake
-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.assertThat
-import org.junit.Before
-import org.junit.Test
-import org.junit.runner.RunWith
-import org.mockito.ArgumentMatchers.anyInt
-import org.mockito.Mockito.verify
-import org.mockito.kotlin.any
-
-@SmallTest
-@EnableFlags(FLAG_GLANCEABLE_HUB_V2)
-@RunWith(AndroidJUnit4::class)
-class CommunalToDreamButtonViewModelTest : SysuiTestCase() {
- private val kosmos = testKosmos()
- private val testScope = kosmos.testScope
- private val underTest: CommunalToDreamButtonViewModel by lazy {
- kosmos.communalToDreamButtonViewModel
- }
-
- @Before
- fun setUp() {
- kosmos.fakeFeatureFlagsClassic.set(COMMUNAL_SERVICE_ENABLED, true)
- underTest.activateIn(testScope)
- }
-
- @Test
- fun shouldShowDreamButtonOnHub_trueWhenPluggedIn() =
- with(kosmos) {
- runTest {
- batteryController.fake._isPluggedIn = true
- runCurrent()
-
- assertThat(underTest.shouldShowDreamButtonOnHub).isTrue()
- }
- }
-
- @Test
- fun shouldShowDreamButtonOnHub_falseWhenNotPluggedIn() =
- with(kosmos) {
- runTest {
- batteryController.fake._isPluggedIn = false
-
- assertThat(underTest.shouldShowDreamButtonOnHub).isFalse()
- }
- }
-
- @Test
- fun onShowDreamButtonTap_dreamsEnabled_startsDream() =
- with(kosmos) {
- runTest {
- val currentUser = fakeUserRepository.asMainUser()
- kosmos.fakeSettings.putIntForUser(
- Settings.Secure.SCREENSAVER_ENABLED,
- 1,
- currentUser.id,
- )
- runCurrent()
-
- underTest.onShowDreamButtonTap()
- runCurrent()
-
- verify(dreamManager).startDream()
- }
- }
-
- @Test
- fun onShowDreamButtonTap_dreamsDisabled_startsActivity() =
- with(kosmos) {
- runTest {
- val currentUser = fakeUserRepository.asMainUser()
- kosmos.fakeSettings.putIntForUser(
- Settings.Secure.SCREENSAVER_ENABLED,
- 0,
- currentUser.id,
- )
- runCurrent()
-
- underTest.onShowDreamButtonTap()
- runCurrent()
-
- verify(activityStarter).postStartActivityDismissingKeyguard(any(), anyInt())
- }
- }
-
- @Test
- fun shouldShowDreamButtonTooltip_trueWhenNotDismissedAndHubOnboardingDismissed() =
- kosmos.runTest {
- setSelectedUser(MAIN_USER)
- fakeCommunalPrefsRepository.setHubOnboardingDismissed(MAIN_USER)
- runCurrent()
-
- assertThat(underTest.shouldShowTooltip).isTrue()
- }
-
- @Test
- fun shouldShowDreamButtonTooltip_falseWhenNotDismissedAndHubOnboardingNotDismissed() =
- kosmos.runTest {
- runCurrent()
- assertThat(underTest.shouldShowTooltip).isFalse()
- }
-
- @Test
- fun shouldShowDreamButtonTooltip_falseWhenDismissed() =
- kosmos.runTest {
- setSelectedUser(MAIN_USER)
- fakeCommunalPrefsRepository.setDreamButtonTooltipDismissed(MAIN_USER)
- runCurrent()
-
- assertThat(underTest.shouldShowTooltip).isFalse()
- }
-
- @Test
- fun onShowDreamButtonTap_eventLogged() =
- with(kosmos) {
- runTest {
- underTest.onShowDreamButtonTap()
- runCurrent()
-
- assertThat(uiEventLoggerFake[0].eventId)
- .isEqualTo(CommunalUiEvent.COMMUNAL_HUB_SHOW_DREAM_BUTTON_TAP.id)
- }
- }
-
- private suspend fun setSelectedUser(user: UserInfo) {
- with(kosmos.fakeUserRepository) {
- setUserInfos(listOf(user))
- setSelectedUserInfo(user)
- }
- kosmos.fakeUserTracker.set(userInfos = listOf(user), selectedUserIndex = 0)
- }
-}
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..8dc7a331dc2d 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
@@ -32,6 +32,8 @@ import com.android.systemui.Flags.FLAG_GLANCEABLE_HUB_DIRECT_EDIT_MODE
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.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.repository.FakeCommunalMediaRepository
import com.android.systemui.communal.data.repository.FakeCommunalSceneRepository
@@ -48,6 +50,7 @@ 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.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 +76,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
@@ -96,6 +101,7 @@ import com.android.systemui.statusbar.KeyguardIndicationController
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.settings.fakeSettings
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.flow.flowOf
import kotlinx.coroutines.test.advanceTimeBy
@@ -940,6 +946,36 @@ class CommunalViewModelTest(flags: FlagsParameterization) : SysuiTestCase() {
assertThat(isUiBlurred).isFalse()
}
+ @Test
+ @EnableFlags(FLAG_GLANCEABLE_HUB_V2)
+ fun swipeToCommunal() =
+ kosmos.runTest {
+ setCommunalV2ConfigEnabled(true)
+ val mainUser = fakeUserRepository.asMainUser()
+ fakeKeyguardRepository.setKeyguardShowing(true)
+ fakeUserRepository.setUserUnlocked(mainUser.id, true)
+ fakeUserTracker.set(userInfos = listOf(mainUser), selectedUserIndex = 0)
+ fakeSettings.putIntForUser(
+ Settings.Secure.SCREENSAVER_ACTIVATE_ON_SLEEP,
+ 1,
+ mainUser.id,
+ )
+
+ val viewModel = createViewModel()
+ val swipeToHubEnabled by collectLastValue(viewModel.swipeToHubEnabled)
+ assertThat(swipeToHubEnabled).isFalse()
+
+ batteryRepository.fake.setDevicePluggedIn(true)
+ 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/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/PerDisplayInstanceRepositoryImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/display/data/repository/PerDisplayInstanceRepositoryImplTest.kt
new file mode 100644
index 000000000000..e41d46ce90af
--- /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.eq
+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(), eq(underTest))
+ }
+
+ 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/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/ui/viewmodel/GlanceableHubToPrimaryBouncerTransitionViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/GlanceableHubToPrimaryBouncerTransitionViewModelTest.kt
index 6b9e23abd9a4..135e9a55e8b5 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/GlanceableHubToPrimaryBouncerTransitionViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/GlanceableHubToPrimaryBouncerTransitionViewModelTest.kt
@@ -16,28 +16,46 @@
package com.android.systemui.keyguard.ui.viewmodel
+import android.content.res.mainResources
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.FLAG_GLANCEABLE_HUB_BLURRED_BACKGROUND
+import com.android.systemui.Flags.FLAG_GLANCEABLE_HUB_V2
import com.android.systemui.SysuiTestCase
+import com.android.systemui.communal.domain.interactor.communalInteractor
+import com.android.systemui.communal.domain.interactor.communalSceneInteractor
+import com.android.systemui.communal.domain.interactor.setCommunalV2ConfigEnabled
+import com.android.systemui.communal.shared.model.CommunalScenes
import com.android.systemui.flags.DisableSceneContainer
import com.android.systemui.keyguard.shared.model.KeyguardState
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.collectValues
+import com.android.systemui.kosmos.runCurrent
import com.android.systemui.kosmos.runTest
+import com.android.systemui.statusbar.policy.keyguardStateController
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
+import org.mockito.kotlin.whenever
@SmallTest
@RunWith(AndroidJUnit4::class)
class GlanceableHubToPrimaryBouncerTransitionViewModelTest : SysuiTestCase() {
- private val kosmos = testKosmos()
+ private val kosmos =
+ testKosmos().apply { mainResources = mContext.orCreateTestableResources.resources }
private val underTest by lazy { kosmos.glanceableHubToPrimaryBouncerTransitionViewModel }
+ @Before
+ fun setUp() {
+ with(kosmos) { setCommunalV2ConfigEnabled(true) }
+ }
+
@Test
@DisableSceneContainer
@DisableFlags(FLAG_GLANCEABLE_HUB_BLURRED_BACKGROUND)
@@ -84,4 +102,81 @@ class GlanceableHubToPrimaryBouncerTransitionViewModelTest : SysuiTestCase() {
},
)
}
+
+ @Test
+ @DisableSceneContainer
+ @DisableFlags(FLAG_GLANCEABLE_HUB_V2)
+ fun willDelayBouncerAppearAnimation_flagDisabled_isFalse() =
+ kosmos.runTest {
+ // keyguard rotation is not allowed on device.
+ whenever(keyguardStateController.isKeyguardScreenRotationAllowed()).thenReturn(false)
+
+ val isIdleOnCommunal by collectLastValue(communalInteractor.isIdleOnCommunal)
+ communalSceneInteractor.changeScene(CommunalScenes.Communal, "test")
+ runCurrent()
+ // Device is idle on communal.
+ assertThat(isIdleOnCommunal).isTrue()
+
+ // in landscape
+ assertThat(underTest.willDelayAppearAnimation(isLandscape = true)).isFalse()
+ // in portrait
+ assertThat(underTest.willDelayAppearAnimation(isLandscape = false)).isFalse()
+ }
+
+ @Test
+ @DisableSceneContainer
+ @EnableFlags(FLAG_GLANCEABLE_HUB_V2)
+ fun willDelayBouncerAppearAnimation_keyguardRotationAllowed_isFalse() =
+ kosmos.runTest {
+ // Keyguard rotation is allowed on device.
+ whenever(keyguardStateController.isKeyguardScreenRotationAllowed()).thenReturn(true)
+
+ val isIdleOnCommunal by collectLastValue(communalInteractor.isIdleOnCommunal)
+ communalSceneInteractor.changeScene(CommunalScenes.Communal, "test")
+ runCurrent()
+ // Device is idle on communal.
+ assertThat(isIdleOnCommunal).isTrue()
+
+ // in landscape
+ assertThat(underTest.willDelayAppearAnimation(isLandscape = true)).isFalse()
+ // in portrait
+ assertThat(underTest.willDelayAppearAnimation(isLandscape = false)).isFalse()
+ }
+
+ @Test
+ @DisableSceneContainer
+ @EnableFlags(FLAG_GLANCEABLE_HUB_V2)
+ fun willDelayBouncerAppearAnimation_isNotIdleOnCommunal_isFalse() =
+ kosmos.runTest {
+ whenever(keyguardStateController.isKeyguardScreenRotationAllowed()).thenReturn(false)
+
+ val isIdleOnCommunal by collectLastValue(communalInteractor.isIdleOnCommunal)
+ communalSceneInteractor.changeScene(CommunalScenes.Blank, "test")
+ runCurrent()
+ // Device is not on communal.
+ assertThat(isIdleOnCommunal).isFalse()
+
+ // in landscape
+ assertThat(underTest.willDelayAppearAnimation(isLandscape = true)).isFalse()
+ // in portrait
+ assertThat(underTest.willDelayAppearAnimation(isLandscape = false)).isFalse()
+ }
+
+ @Test
+ @DisableSceneContainer
+ @EnableFlags(FLAG_GLANCEABLE_HUB_V2)
+ fun willDelayBouncerAppearAnimation_isIdleOnCommunalAndKeyguardRotationIsNotAllowed() =
+ kosmos.runTest {
+ whenever(keyguardStateController.isKeyguardScreenRotationAllowed()).thenReturn(false)
+ val isIdleOnCommunal by collectLastValue(communalInteractor.isIdleOnCommunal)
+ communalSceneInteractor.changeScene(CommunalScenes.Communal, "test")
+ runCurrent()
+ // Device is idle on communal.
+ assertThat(isIdleOnCommunal).isTrue()
+
+ // Will delay in landscape
+ assertThat(underTest.willDelayAppearAnimation(isLandscape = true)).isTrue()
+ // Won't delay in portrait
+ assertThat(underTest.willDelayAppearAnimation(isLandscape = false)).isFalse()
+ }
}
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/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..b82f5fce9e14
--- /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 = 10L
+ const val FLAG_2 = 20L
+ }
+}
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..09588f9f3751 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,20 +31,11 @@ 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, 3L to true)
assertThat(underTest.flags and 1L).isNotEqualTo(0L)
assertThat(underTest.flags and 2L).isEqualTo(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/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/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/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/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 764068ec1bf5..4a304071ee97 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/NotificationShadeWindowControllerImplTest.java
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/NotificationShadeWindowControllerImplTest.java
@@ -41,7 +41,6 @@ import android.content.pm.ActivityInfo;
import android.content.res.Configuration;
import android.graphics.Rect;
import android.platform.test.flag.junit.FlagsParameterization;
-import android.provider.Settings;
import android.testing.TestableLooper.RunWithLooper;
import android.view.View;
import android.view.WindowManager;
@@ -71,7 +70,6 @@ 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;
@@ -84,12 +82,12 @@ import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import org.mockito.Spy;
-import java.util.List;
-import java.util.concurrent.Executor;
-
import platform.test.runner.parameterized.ParameterizedAndroidJunit4;
import platform.test.runner.parameterized.Parameters;
+import java.util.List;
+import java.util.concurrent.Executor;
+
@RunWith(ParameterizedAndroidJunit4.class)
@RunWithLooper(setAsMainLooper = true)
@SmallTest
@@ -113,7 +111,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 +132,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 +165,6 @@ public class NotificationShadeWindowControllerImplTest extends SysuiTestCase {
() -> mSelectedUserInteractor,
mUserTracker,
mKosmos.getNotificationShadeWindowModel(),
- mSecureSettings,
mKosmos::getCommunalInteractor,
mKosmos.getShadeLayoutParams());
mNotificationShadeWindowController.setScrimsVisibilityListener((visibility) -> {});
@@ -355,19 +348,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);
@@ -410,6 +390,19 @@ public class NotificationShadeWindowControllerImplTest extends SysuiTestCase {
}
@Test
+ public void hubOrientationAware_layoutParamsUpdated() {
+ mNotificationShadeWindowController.setKeyguardShowing(false);
+ mNotificationShadeWindowController.setBouncerShowing(false);
+ mNotificationShadeWindowController.setGlanceableHubOrientationAware(true);
+ when(mKeyguardStateController.isKeyguardScreenRotationAllowed()).thenReturn(false);
+ mNotificationShadeWindowController.onConfigChanged(new Configuration());
+
+ verify(mWindowManager, atLeastOnce()).updateViewLayout(any(), mLayoutParameters.capture());
+ assertThat(mLayoutParameters.getValue().screenOrientation)
+ .isEqualTo(ActivityInfo.SCREEN_ORIENTATION_USER);
+ }
+
+ @Test
public void batchApplyWindowLayoutParams_doesNotDispatchEvents() {
mNotificationShadeWindowController.setForceDozeBrightness(true);
verify(mWindowManager).updateViewLayout(any(), any());
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 f4a43a454a6f..ddad230f04e9 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
@@ -151,9 +151,17 @@ class ClockRegistryTest : SysuiTestCase() {
create: (ClockId) -> ClockController = ::failFactory,
getPickerConfig: (ClockSettings) -> ClockPickerConfig = ::failPickerConfig,
): FakeClockPlugin {
- metadata.add(ClockMetadata(id))
- createCallbacks[id] = create
- pickerConfigs[id] = getPickerConfig
+ return addClock(ClockMetadata(id), create, getPickerConfig)
+ }
+
+ fun addClock(
+ metadata: ClockMetadata,
+ create: (ClockId) -> ClockController = ::failFactory,
+ getPickerConfig: (ClockSettings) -> ClockPickerConfig = ::failPickerConfig,
+ ): FakeClockPlugin {
+ this.metadata.add(metadata)
+ createCallbacks[metadata.clockId] = create
+ pickerConfigs[metadata.clockId] = getPickerConfig
return this
}
}
@@ -203,28 +211,40 @@ class ClockRegistryTest : SysuiTestCase() {
val plugin1 = FakeClockPlugin().addClock("clock_1").addClock("clock_2")
val lifecycle1 = FakeLifecycle("1", plugin1)
- val plugin2 = FakeClockPlugin().addClock("clock_3").addClock("clock_4")
+ val plugin2 =
+ FakeClockPlugin()
+ .addClock(ClockMetadata("clock_3", isDeprecated = false))
+ .addClock(ClockMetadata("clock_4", isDeprecated = true))
val lifecycle2 = FakeLifecycle("2", plugin2)
pluginListener.onPluginLoaded(plugin1, mockContext, lifecycle1)
pluginListener.onPluginLoaded(plugin2, mockContext, lifecycle2)
- val list = registry.getClocks()
assertEquals(
- list.toSet(),
setOf(
ClockMetadata(DEFAULT_CLOCK_ID),
ClockMetadata("clock_1"),
ClockMetadata("clock_2"),
ClockMetadata("clock_3"),
- ClockMetadata("clock_4"),
),
+ registry.getClocks().toSet(),
+ )
+
+ assertEquals(
+ setOf(
+ ClockMetadata(DEFAULT_CLOCK_ID),
+ ClockMetadata("clock_1"),
+ ClockMetadata("clock_2"),
+ ClockMetadata("clock_3"),
+ ClockMetadata("clock_4", isDeprecated = true),
+ ),
+ registry.getClocks(includeDeprecated = true).toSet(),
)
}
@Test
fun noPlugins_createDefaultClock() {
val clock = registry.createCurrentClock()
- assertEquals(clock, mockDefaultClock)
+ assertEquals(mockDefaultClock, clock)
}
@Test
@@ -242,18 +262,18 @@ class ClockRegistryTest : SysuiTestCase() {
pluginListener.onPluginLoaded(plugin2, mockContext, lifecycle2)
val list = registry.getClocks()
assertEquals(
- list.toSet(),
setOf(
ClockMetadata(DEFAULT_CLOCK_ID),
ClockMetadata("clock_1"),
ClockMetadata("clock_2"),
),
+ list.toSet(),
)
- assertEquals(registry.createExampleClock("clock_1"), mockClock)
- assertEquals(registry.createExampleClock("clock_2"), mockClock)
- assertEquals(registry.getClockPickerConfig("clock_1"), pickerConfig)
- assertEquals(registry.getClockPickerConfig("clock_2"), pickerConfig)
+ assertEquals(mockClock, registry.createExampleClock("clock_1"))
+ assertEquals(mockClock, registry.createExampleClock("clock_2"))
+ assertEquals(pickerConfig, registry.getClockPickerConfig("clock_1"))
+ assertEquals(pickerConfig, registry.getClockPickerConfig("clock_2"))
verify(lifecycle1, never()).unloadPlugin()
verify(lifecycle2, times(2)).unloadPlugin()
}
@@ -305,7 +325,7 @@ class ClockRegistryTest : SysuiTestCase() {
pluginListener.onPluginUnloaded(plugin2, lifecycle2)
val clock = registry.createCurrentClock()
- assertEquals(clock, mockDefaultClock)
+ assertEquals(mockDefaultClock, clock)
}
@Test
@@ -482,13 +502,13 @@ class ClockRegistryTest : SysuiTestCase() {
// Verify all plugins were correctly loaded into the registry
assertEquals(
- registry.getClocks().toSet(),
setOf(
ClockMetadata("DEFAULT"),
ClockMetadata("clock_2"),
ClockMetadata("clock_3"),
ClockMetadata("clock_4"),
),
+ registry.getClocks().toSet(),
)
}
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 9eba410ffdb5..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 3eec1cd2d54c..f54c28f4295b 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerTest.java
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerTest.java
@@ -200,6 +200,7 @@ public class NotificationLockscreenUserManagerTest extends SysuiTestCase {
private NotificationEntry mSecondaryUserNotif;
private NotificationEntry mWorkProfileNotif;
private NotificationEntry mSensitiveContentNotif;
+ private NotificationEntry mSensitiveNotifWithOldCreationTime;
private long mSensitiveNotifPostTime;
private final FakeFeatureFlagsClassic mFakeFeatureFlags = new FakeFeatureFlagsClassic();
private final FakeSystemClock mFakeSystemClock = new FakeSystemClock();
@@ -274,6 +275,20 @@ public class NotificationLockscreenUserManagerTest extends SysuiTestCase {
.setSensitiveContent(true)
.setVisibilityOverride(VISIBILITY_NO_OVERRIDE).build());
mSensitiveNotifPostTime = mSensitiveContentNotif.getSbn().getNotification().getWhen();
+
+ mSensitiveNotifWithOldCreationTime = new NotificationEntryBuilder()
+ .setNotification(notifWithPrivateVisibility)
+ .setUser(new UserHandle(mCurrentUser.id))
+ .setPostTime(System.currentTimeMillis())
+ // creation time of at least -2 hours, no matter what the current value of
+ // SystemClock.currentTimeMillis
+ .setCreationTime(-1 * TimeUnit.HOURS.toMillis(2))
+ .build();
+ mSensitiveNotifWithOldCreationTime.setRanking(
+ new RankingBuilder(mCurrentUserNotif.getRanking())
+ .setChannel(channel)
+ .setSensitiveContent(true)
+ .setVisibilityOverride(VISIBILITY_NO_OVERRIDE).build());
when(mNotifCollection.getEntry(mWorkProfileNotif.getKey())).thenReturn(mWorkProfileNotif);
when(mKeyguardInteractorLazy.get()).thenReturn(mKeyguardInteractor);
when(mKeyguardInteractor.isKeyguardDismissible())
@@ -653,6 +668,23 @@ public class NotificationLockscreenUserManagerTest extends SysuiTestCase {
@Test
@EnableFlags(LockscreenOtpRedaction.FLAG_NAME)
+ public void testNewSensitiveNotification_notRedactedIfOldCreationTime() {
+ // Allow private notifications for this user
+ mSettings.putIntForUser(LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS, 1,
+ mCurrentUser.id);
+ changeSetting(LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS);
+ mLockscreenUserManager.mLocked.set(true);
+ // Claim the device was last unlocked 1 hour ago. Old enough to redact, but newer than the
+ // old creation time in the notification (which is -2 hours)
+ mLockscreenUserManager.mLastLockTime
+ .set(mSensitiveNotifPostTime - TimeUnit.HOURS.toMillis(1));
+ mLockscreenUserManager.mConnectedToWifi.set(false);
+ assertEquals(REDACTION_TYPE_NONE,
+ mLockscreenUserManager.getRedactionType(mSensitiveNotifWithOldCreationTime));
+ }
+
+ @Test
+ @EnableFlags(LockscreenOtpRedaction.FLAG_NAME)
public void testHasSensitiveContent_redacted() {
// Allow private notifications for this user
mSettings.putIntForUser(LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS, 1,
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..8165d45c93df 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/NotificationShadeDepthControllerTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/NotificationShadeDepthControllerTest.kt
@@ -34,6 +34,7 @@ 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.shared.Flags as SharedFlags
import com.android.systemui.statusbar.phone.BiometricUnlockController
import com.android.systemui.statusbar.phone.DozeParameters
import com.android.systemui.statusbar.phone.ScrimController
@@ -43,10 +44,11 @@ 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
-import com.android.systemui.wallpapers.domain.interactor.WallpaperInteractor
import com.android.systemui.window.domain.interactor.WindowRootViewBlurInteractor
import com.android.wm.shell.appzoomout.AppZoomOut
import com.google.common.truth.Truth.assertThat
+import java.util.Optional
+import java.util.function.Consumer
import org.junit.Before
import org.junit.Rule
import org.junit.Test
@@ -67,8 +69,6 @@ import org.mockito.Mockito.reset
import org.mockito.Mockito.verify
import org.mockito.Mockito.`when`
import org.mockito.junit.MockitoJUnit
-import java.util.Optional
-import java.util.function.Consumer
@RunWith(AndroidJUnit4::class)
@RunWithLooper
@@ -76,7 +76,6 @@ import java.util.function.Consumer
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
@@ -85,7 +84,6 @@ class NotificationShadeDepthControllerTest : SysuiTestCase() {
@Mock private lateinit var keyguardInteractor: KeyguardInteractor
@Mock private lateinit var choreographer: Choreographer
@Mock private lateinit var wallpaperController: WallpaperController
- @Mock private lateinit var wallpaperInteractor: WallpaperInteractor
@Mock private lateinit var notificationShadeWindowController: NotificationShadeWindowController
@Mock private lateinit var dumpManager: DumpManager
@Mock private lateinit var appZoomOutOptional: Optional<AppZoomOut>
@@ -130,14 +128,12 @@ class NotificationShadeDepthControllerTest : SysuiTestCase() {
keyguardInteractor,
choreographer,
wallpaperController,
- wallpaperInteractor,
notificationShadeWindowController,
dozeParameters,
context,
ResourcesSplitShadeStateController(),
windowRootViewBlurInteractor,
appZoomOutOptional,
- applicationScope,
dumpManager,
configurationController,
)
@@ -314,21 +310,19 @@ class NotificationShadeDepthControllerTest : SysuiTestCase() {
}
@Test
- fun onDozeAmountChanged_doesNotApplyBlurWithAmbientAod() {
- notificationShadeDepthController.wallpaperSupportsAmbientMode = false
-
+ @DisableFlags(SharedFlags.FLAG_AMBIENT_AOD)
+ fun onDozeAmountChanged_appliesBlur() {
statusBarStateListener.onDozeAmountChanged(1f, 1f)
notificationShadeDepthController.updateBlurCallback.doFrame(0)
- verify(blurUtils).applyBlur(any(), eq(0), eq(false))
+ verify(blurUtils).applyBlur(any(), eq(maxBlur), eq(false))
}
@Test
- fun onDozeAmountChanged_appliesBlurWithAmbientAod() {
- notificationShadeDepthController.wallpaperSupportsAmbientMode = true
-
+ @EnableFlags(SharedFlags.FLAG_AMBIENT_AOD)
+ fun onDozeAmountChanged_doesNotApplyBlurWithAmbientAod() {
statusBarStateListener.onDozeAmountChanged(1f, 1f)
notificationShadeDepthController.updateBlurCallback.doFrame(0)
- verify(blurUtils).applyBlur(any(), eq(maxBlur), eq(false))
+ verify(blurUtils).applyBlur(any(), eq(0), eq(false))
}
@Test
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/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..f06244f4f637 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,62 @@ 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
+ fun visibleChipKeys_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 +975,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 +995,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 +1010,7 @@ class OngoingActivityChipsWithNotifsViewModelTest : SysuiTestCase() {
val callNotificationKey = "call"
val firstIcon = createStatusBarIconViewOrNull()
val secondIcon = createStatusBarIconViewOrNull()
+ val thirdIcon = createStatusBarIconViewOrNull()
addOngoingCallState(key = callNotificationKey)
activeNotificationListRepository.addNotifs(
listOf(
@@ -922,27 +1026,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 +1064,41 @@ class OngoingActivityChipsWithNotifsViewModelTest : SysuiTestCase() {
)
assertIsScreenRecordChip(latest!!.primary)
- assertIsCallChip(latest!!.secondary, callNotificationKey)
+ assertIsCallChip(latest!!.secondary, callNotificationKey, context)
assertThat(unused).isEqualTo(MultipleOngoingActivityChipsModel())
}
+ @Test
+ fun visibleChipKeys_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 +1115,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 +1160,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 +1191,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 +1216,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 +1253,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 +1272,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 +1305,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 +1326,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 +1334,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 +1350,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/PhysicsPropertyAnimatorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/PhysicsPropertyAnimatorTest.kt
index 56cd72e7725f..0bb473721446 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/PhysicsPropertyAnimatorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/PhysicsPropertyAnimatorTest.kt
@@ -15,19 +15,22 @@
*/
package com.android.systemui.statusbar.notification
+import android.animation.AnimatorTestRule
import android.util.FloatProperty
import android.util.Property
import android.view.View
-import androidx.dynamicanimation.animation.DynamicAnimation
+
import androidx.test.annotation.UiThreadTest
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
+import com.android.internal.dynamicanimation.animation.DynamicAnimation
import com.android.systemui.SysuiTestCase
import com.android.systemui.res.R
import com.android.systemui.statusbar.notification.stack.AnimationProperties
import com.android.systemui.statusbar.notification.stack.ViewState
import org.junit.Assert
import org.junit.Before
+import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.Mockito
@@ -50,6 +53,8 @@ class PhysicsPropertyAnimatorTest : SysuiTestCase() {
return _value
}
}
+ @get:Rule
+ val animatorTestRule = AnimatorTestRule(this)
private val property: PhysicsProperty =
PhysicsProperty(R.id.scale_x_animator_tag, effectiveProperty)
private var finishListener: DynamicAnimation.OnAnimationEndListener? = null
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/ConversationCoordinatorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/coordinator/ConversationCoordinatorTest.kt
index 285326421a14..fa185bcabf83 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/coordinator/ConversationCoordinatorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/coordinator/ConversationCoordinatorTest.kt
@@ -18,6 +18,7 @@ package com.android.systemui.statusbar.notification.collection.coordinator
import android.app.Flags
import android.app.NotificationChannel
+import android.app.NotificationChannel.SYSTEM_RESERVED_IDS
import android.app.NotificationManager.IMPORTANCE_DEFAULT
import android.app.NotificationManager.IMPORTANCE_HIGH
import android.app.NotificationManager.IMPORTANCE_LOW
@@ -124,6 +125,14 @@ class ConversationCoordinatorTest : SysuiTestCase() {
}
@Test
+ fun testPrioritySectioner_doesNotClaim_classifiedConversation() {
+ val sectioner = coordinator.priorityPeopleSectioner
+ for (id in SYSTEM_RESERVED_IDS) {
+ assertFalse(sectioner.isInSection(makeClassifiedConversation(id)))
+ }
+ }
+
+ @Test
fun testPromotesImportantConversations() {
assertTrue(promoter.shouldPromoteToTopLevel(makeEntryOfPeopleType(TYPE_IMPORTANT_PERSON)))
assertFalse(promoter.shouldPromoteToTopLevel(makeEntryOfPeopleType(TYPE_FULL_PERSON)))
@@ -166,6 +175,14 @@ class ConversationCoordinatorTest : SysuiTestCase() {
}
@Test
+ fun testAlertingSectioner_doesNotClaim_classifiedConversation() {
+ val sectioner = coordinator.peopleAlertingSectioner
+ for (id in SYSTEM_RESERVED_IDS) {
+ assertFalse(sectioner.isInSection(makeClassifiedConversation(id)))
+ }
+ }
+
+ @Test
fun testInAlertingPeopleSectionWhenTheImportanceIsAtLeastDefault() {
// GIVEN
val alertingEntry = makeEntryOfPeopleType(TYPE_PERSON) { setImportance(IMPORTANCE_DEFAULT) }
@@ -186,6 +203,15 @@ class ConversationCoordinatorTest : SysuiTestCase() {
@Test
@DisableFlags(Flags.FLAG_SORT_SECTION_BY_TIME)
+ fun testSilentSectioner_doesNotClaim_classifiedConversation() {
+ val sectioner = coordinator.peopleSilentSectioner
+ for (id in SYSTEM_RESERVED_IDS) {
+ assertFalse(sectioner.isInSection(makeClassifiedConversation(id)))
+ }
+ }
+
+ @Test
+ @DisableFlags(Flags.FLAG_SORT_SECTION_BY_TIME)
fun testInSilentPeopleSectionWhenTheImportanceIsLowerThanDefault() {
// GIVEN
val silentEntry = makeEntryOfPeopleType(TYPE_PERSON) { setImportance(IMPORTANCE_LOW) }
@@ -280,4 +306,17 @@ class ConversationCoordinatorTest : SysuiTestCase() {
assertEquals(type, peopleNotificationIdentifier.getPeopleNotificationType(entry))
return entry
}
+
+ private fun makeClassifiedConversation(channelId: String): NotificationEntry {
+ val channel = NotificationChannel(channelId, channelId, IMPORTANCE_LOW)
+ val entry =
+ NotificationEntryBuilder()
+ .updateRanking {
+ it.setIsConversation(true)
+ it.setShortcutInfo(mock())
+ it.setChannel(channel)
+ }
+ .build()
+ return entry
+ }
}
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/OriginalUnseenKeyguardCoordinatorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/coordinator/OriginalUnseenKeyguardCoordinatorTest.kt
index d5b2d3a68f1e..4e13dcd7576f 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/coordinator/OriginalUnseenKeyguardCoordinatorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/coordinator/OriginalUnseenKeyguardCoordinatorTest.kt
@@ -20,7 +20,6 @@ import android.app.Notification.MediaStyle
import android.media.session.MediaSession
import android.platform.test.flag.junit.FlagsParameterization
import android.provider.Settings
-import android.service.notification.StatusBarNotification
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.dump.dumpManager
@@ -222,16 +221,19 @@ class OriginalUnseenKeyguardCoordinatorTest(flags: FlagsParameterization) : Sysu
mock<ExpandableNotificationRow>().apply {
whenever(isMediaRow).thenReturn(true)
}
- sbn = SbnBuilder().setNotification(
- Notification.Builder(context, "channel").setStyle(
- MediaStyle().setMediaSession(
- MediaSession(
- context,
- "tag"
- ).sessionToken
+ sbn =
+ SbnBuilder()
+ .setNotification(
+ Notification.Builder(context, "channel")
+ .setStyle(
+ MediaStyle()
+ .setMediaSession(
+ MediaSession(context, "tag").sessionToken
+ )
+ )
+ .build()
)
- ).build()
- ).build()
+ .build()
}
collectionListener.onEntryAdded(fakeEntry)
@@ -631,6 +633,132 @@ class OriginalUnseenKeyguardCoordinatorTest(flags: FlagsParameterization) : Sysu
}
}
+ @Test
+ fun seenNotificationOnKeyguardMarkedAsSeenIfUpdatedBySystemServer() {
+ // GIVEN: Keyguard is showing, not dozing, unseen notification is present
+ keyguardRepository.setKeyguardShowing(true)
+ keyguardRepository.setIsDozing(false)
+ runKeyguardCoordinatorTest {
+ val fakeEntry = NotificationEntryBuilder().build()
+ collectionListener.onEntryAdded(fakeEntry)
+ keyguardTransitionRepository.sendTransitionSteps(
+ from = KeyguardState.AOD,
+ to = KeyguardState.LOCKSCREEN,
+ this.testScheduler,
+ )
+ testScheduler.runCurrent()
+
+ // WHEN: five seconds have passed
+ testScheduler.advanceTimeBy(5.seconds)
+ testScheduler.runCurrent()
+
+ // WHEN: Keyguard is no longer showing
+ keyguardRepository.setKeyguardShowing(false)
+ kosmos.setTransition(
+ sceneTransition = Idle(Scenes.Gone),
+ stateTransition = TransitionStep(KeyguardState.LOCKSCREEN, KeyguardState.GONE),
+ )
+
+ // WHEN: the notification is updated by the Server
+ collectionListener.onEntryUpdated(fakeEntry, UpdateSource.SystemServer)
+ testScheduler.runCurrent()
+
+ // WHEN: Keyguard is shown again
+ keyguardRepository.setKeyguardShowing(true)
+ kosmos.setTransition(
+ sceneTransition = Idle(Scenes.Lockscreen),
+ stateTransition = TransitionStep(KeyguardState.GONE, KeyguardState.AOD),
+ )
+
+ // THEN: The notification is still recognized as "seen" and is filtered out.
+ assertThat(unseenFilter.shouldFilterOut(fakeEntry, 0L)).isTrue()
+ }
+ }
+
+ @Test
+ fun seenNotificationOnKeyguardMarkedAsSeenIfUpdatedBySystemUi() {
+ // GIVEN: Keyguard is showing, not dozing, unseen notification is present
+ keyguardRepository.setKeyguardShowing(true)
+ keyguardRepository.setIsDozing(false)
+ runKeyguardCoordinatorTest {
+ val fakeEntry = NotificationEntryBuilder().build()
+ collectionListener.onEntryAdded(fakeEntry)
+ keyguardTransitionRepository.sendTransitionSteps(
+ from = KeyguardState.AOD,
+ to = KeyguardState.LOCKSCREEN,
+ this.testScheduler,
+ )
+ testScheduler.runCurrent()
+
+ // WHEN: five seconds have passed
+ testScheduler.advanceTimeBy(5.seconds)
+ testScheduler.runCurrent()
+
+ // WHEN: Keyguard is no longer showing
+ keyguardRepository.setKeyguardShowing(false)
+ kosmos.setTransition(
+ sceneTransition = Idle(Scenes.Gone),
+ stateTransition = TransitionStep(KeyguardState.LOCKSCREEN, KeyguardState.GONE),
+ )
+
+ // WHEN: the notification is updated by the SystemUi
+ collectionListener.onEntryUpdated(fakeEntry, UpdateSource.SystemUi)
+ testScheduler.runCurrent()
+
+ // WHEN: Keyguard is shown again
+ keyguardRepository.setKeyguardShowing(true)
+ kosmos.setTransition(
+ sceneTransition = Idle(Scenes.Lockscreen),
+ stateTransition = TransitionStep(KeyguardState.GONE, KeyguardState.AOD),
+ )
+
+ // THEN: The notification is still recognized as "seen" and is filtered out.
+ assertThat(unseenFilter.shouldFilterOut(fakeEntry, 0L)).isTrue()
+ }
+ }
+
+ @Test
+ fun seenNotificationOnKeyguardMarkedAsUnseenIfUpdatedByApp() {
+ // GIVEN: Keyguard is showing, not dozing, unseen notification is present
+ keyguardRepository.setKeyguardShowing(true)
+ keyguardRepository.setIsDozing(false)
+ runKeyguardCoordinatorTest {
+ val fakeEntry = NotificationEntryBuilder().build()
+ collectionListener.onEntryAdded(fakeEntry)
+ keyguardTransitionRepository.sendTransitionSteps(
+ from = KeyguardState.AOD,
+ to = KeyguardState.LOCKSCREEN,
+ this.testScheduler,
+ )
+ testScheduler.runCurrent()
+
+ // WHEN: five seconds have passed
+ testScheduler.advanceTimeBy(5.seconds)
+ testScheduler.runCurrent()
+
+ // WHEN: Keyguard is no longer showing
+ keyguardRepository.setKeyguardShowing(false)
+ kosmos.setTransition(
+ sceneTransition = Idle(Scenes.Gone),
+ stateTransition = TransitionStep(KeyguardState.LOCKSCREEN, KeyguardState.GONE),
+ )
+
+ // WHEN: the notification is updated by the App
+ collectionListener.onEntryUpdated(fakeEntry, UpdateSource.App)
+ testScheduler.runCurrent()
+
+ // WHEN: Keyguard is shown again
+ keyguardRepository.setKeyguardShowing(true)
+ kosmos.setTransition(
+ sceneTransition = Idle(Scenes.Lockscreen),
+ stateTransition = TransitionStep(KeyguardState.GONE, KeyguardState.AOD),
+ )
+
+ // THEN: The notification is now recognized as "unseen" and is not filtered out.
+ assertThat(unseenFilter.shouldFilterOut(fakeEntry, 0L)).isFalse()
+ }
+ }
+
private fun runKeyguardCoordinatorTest(
testBlock: suspend KeyguardCoordinatorTestScope.() -> Unit
) {
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..3116143504eb 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
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/NotificationRowContentBinderImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/row/NotificationRowContentBinderImplTest.kt
index ce3aee1d88d2..31413b06e5fd 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
@@ -41,7 +41,6 @@ import com.android.systemui.statusbar.NotificationLockscreenUserManager.REDACTIO
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,
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..784743740434 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).
@@ -734,7 +739,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/ui/viewmodel/ActivatableNotificationViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/row/ui/viewmodel/ActivatableNotificationViewModelTest.kt
index 961616c99cb1..3d1fdee306f4 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/row/ui/viewmodel/ActivatableNotificationViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/row/ui/viewmodel/ActivatableNotificationViewModelTest.kt
@@ -22,6 +22,8 @@ import com.android.systemui.SysuiTestCase
import com.android.systemui.accessibility.data.repository.FakeAccessibilityRepository
import com.android.systemui.accessibility.domain.interactor.AccessibilityInteractor
import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.testKosmos
+import com.android.systemui.window.domain.interactor.windowRootViewBlurInteractor
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.test.runCurrent
import kotlinx.coroutines.test.runTest
@@ -32,12 +34,16 @@ import org.junit.runner.RunWith
@SmallTest
class ActivatableNotificationViewModelTest : SysuiTestCase() {
+ private val kosmos = testKosmos()
+
// fakes
private val a11yRepo = FakeAccessibilityRepository()
// real impls
private val a11yInteractor = AccessibilityInteractor(a11yRepo)
- private val underTest = ActivatableNotificationViewModel(a11yInteractor)
+ private val windowRootViewBlurInteractor = kosmos.windowRootViewBlurInteractor
+ private val underTest = ActivatableNotificationViewModel(a11yInteractor,
+ windowRootViewBlurInteractor)
@Test
fun isTouchable_whenA11yTouchExplorationDisabled() = runTest {
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..08ecbac1582c 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,6 +34,8 @@ 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 org.junit.Assume
@@ -53,6 +57,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 +137,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 +457,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)
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 4a761917b693..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
@@ -18,18 +18,23 @@ package com.android.systemui.statusbar.notification.stack
import android.platform.test.annotations.DisableFlags
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.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
@@ -53,18 +58,22 @@ private const val HEADS_UP_ABOVE_SCREEN = 80
@RunWith(AndroidJUnit4::class)
@RunWithLooper
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()
private val view: ExpandableView = mock()
- private val viewState: ExpandableViewState =
- ExpandableViewState().apply { height = VIEW_HEIGHT }
+ private lateinit var viewState: ExpandableViewState
private val runnableCaptor: ArgumentCaptor<Runnable> = argumentCaptor()
@Before
fun setUp() {
+ viewState = ExpandableViewState().apply { height = VIEW_HEIGHT }
overrideResource(
R.dimen.go_to_full_shade_appearing_translation,
FULL_SHADE_APPEAR_TRANSLATION,
@@ -75,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
@@ -129,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
@@ -179,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)
@@ -210,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)
@@ -238,6 +305,32 @@ class StackStateAnimatorTest : SysuiTestCase() {
.removeFromTransientContainer()
}
+ @EnableFlags(Flags.FLAG_PHYSICAL_NOTIFICATION_MOVEMENT)
+ @Test
+ fun startAnimationForEvents_startsHeadsUpDisappearAnim_physical() {
+ val disappearDuration = ANIMATION_DURATION_HEADS_UP_DISAPPEAR.toLong()
+ val event = AnimationEvent(view, AnimationEvent.ANIMATION_TYPE_HEADS_UP_DISAPPEAR)
+ clearInvocations(view)
+ stackStateAnimator.startAnimationForEvents(arrayListOf(event), 0)
+
+ verify(view)
+ .performRemoveAnimation(
+ /* duration= */ eq(disappearDuration),
+ /* delay= */ eq(0L),
+ /* translationDirection= */ eq(0f),
+ /* isHeadsUpAnimation= */ eq(true),
+ /* isHeadsUpCycling= */ eq(false),
+ /* onStartedRunnable= */ any(),
+ /* onFinishedRunnable= */ runnableCaptor.capture(),
+ /* animationListener= */ any(),
+ /* clipSide= */ eq(ExpandableView.ClipSide.BOTTOM),
+ )
+
+ runnableCaptor.value.run() // execute the end runnable
+ verify(view, description("should be called at the end of the disappear animation"))
+ .removeFromTransientContainer()
+ }
+
@Test
fun initView_updatesResources() {
// Given: the resource values are initialized in the SSA
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/ViewStateTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/ViewStateTest.kt
index ef415c918f91..6ee1c4ddf8e3 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/ViewStateTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/ViewStateTest.kt
@@ -16,6 +16,7 @@
package com.android.systemui.statusbar.notification.stack
+import android.animation.AnimatorTestRule
import android.animation.ValueAnimator
import android.view.View
import androidx.test.annotation.UiThreadTest
@@ -28,6 +29,7 @@ import com.android.systemui.statusbar.notification.PhysicsPropertyAnimator
import com.android.systemui.statusbar.notification.PhysicsPropertyAnimator.Companion.TAG_ANIMATOR_TRANSLATION_Y
import com.android.systemui.statusbar.notification.PhysicsPropertyAnimator.Companion.Y_TRANSLATION
import org.junit.Assert
+import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
import kotlin.math.log2
@@ -38,6 +40,8 @@ import kotlin.math.sqrt
@UiThreadTest
class ViewStateTest : SysuiTestCase() {
private val viewState = ViewState(true /* usePhysicsForMovement */)
+ @get:Rule
+ val animatorTestRule = AnimatorTestRule(this)
@Suppress("DIVISION_BY_ZERO")
@Test
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/data/repository/MobileRepositorySwitcherKairosTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileRepositorySwitcherKairosTest.kt
new file mode 100644
index 000000000000..80f4b2ce7b10
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileRepositorySwitcherKairosTest.kt
@@ -0,0 +1,173 @@
+/*
+ * 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.data.repository
+
+import android.telephony.SubscriptionInfo
+import android.telephony.SubscriptionManager
+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.demoModeController
+import com.android.systemui.demomode.DemoMode
+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.useUnconfinedTestDispatcher
+import com.android.systemui.statusbar.pipeline.mobile.data.model.SubscriptionModel
+import com.android.systemui.statusbar.pipeline.mobile.data.repository.demo.DemoMobileConnectionsRepositoryKairos
+import com.android.systemui.statusbar.pipeline.mobile.data.repository.demo.validMobileEvent
+import com.android.systemui.testKosmos
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.flow.MutableStateFlow
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Mockito.verify
+import org.mockito.kotlin.any
+import org.mockito.kotlin.argumentCaptor
+import org.mockito.kotlin.doReturn
+import org.mockito.kotlin.mock
+import org.mockito.kotlin.stub
+
+/**
+ * The switcher acts as a dispatcher to either the `prod` or `demo` versions of the repository
+ * interface it's switching on. These tests just need to verify that the entire interface properly
+ * switches over when the value of `demoMode` changes
+ */
+@OptIn(ExperimentalKairosApi::class)
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class MobileRepositorySwitcherKairosTest : SysuiTestCase() {
+ private val kosmos =
+ testKosmos().apply {
+ useUnconfinedTestDispatcher()
+ demoModeController.stub {
+ // Never start in demo mode
+ on { isInDemoMode } doReturn false
+ }
+ wifiDataSource.stub { on { wifiEvents } doReturn MutableStateFlow(null) }
+ }
+
+ private val Kosmos.underTest
+ get() = mobileRepositorySwitcherKairos
+
+ private val Kosmos.realRepo
+ get() = mobileConnectionsRepositoryKairosImpl
+
+ private fun runTest(block: suspend KairosTestScope.() -> Unit) =
+ kosmos.run { runKairosTest { block() } }
+
+ @Test
+ fun activeRepoMatchesDemoModeSetting() = runTest {
+ demoModeController.stub { on { isInDemoMode } doReturn false }
+
+ val latest by underTest.activeRepo.collectLastValue()
+
+ assertThat(latest).isEqualTo(realRepo)
+
+ startDemoMode()
+
+ assertThat(latest).isInstanceOf(DemoMobileConnectionsRepositoryKairos::class.java)
+
+ finishDemoMode()
+
+ assertThat(latest).isEqualTo(realRepo)
+ }
+
+ @Test
+ fun subscriptionListUpdatesWhenDemoModeChanges() = runTest {
+ demoModeController.stub { on { isInDemoMode } doReturn false }
+
+ subscriptionManager.stub {
+ on { completeActiveSubscriptionInfoList } doReturn listOf(SUB_1, SUB_2)
+ }
+
+ val latest by underTest.subscriptions.collectLastValue()
+
+ // The real subscriptions has 2 subs
+ getSubscriptionCallback().onSubscriptionsChanged()
+
+ assertThat(latest).isEqualTo(listOf(MODEL_1, MODEL_2))
+
+ // Demo mode turns on, and we should see only the demo subscriptions
+ startDemoMode()
+ demoModeMobileConnectionDataSourceKairos.fake.mobileEvents.emit(validMobileEvent(subId = 3))
+
+ // Demo mobile connections repository makes arbitrarily-formed subscription info
+ // objects, so just validate the data we care about
+ assertThat(latest).hasSize(1)
+ assertThat(latest!!.first().subscriptionId).isEqualTo(3)
+
+ finishDemoMode()
+
+ assertThat(latest).isEqualTo(listOf(MODEL_1, MODEL_2))
+ }
+
+ private fun KairosTestScope.startDemoMode() {
+ demoModeController.stub { on { isInDemoMode } doReturn true }
+ getDemoModeCallback().onDemoModeStarted()
+ }
+
+ private fun KairosTestScope.finishDemoMode() {
+ demoModeController.stub { on { isInDemoMode } doReturn false }
+ getDemoModeCallback().onDemoModeFinished()
+ }
+
+ private fun KairosTestScope.getSubscriptionCallback():
+ SubscriptionManager.OnSubscriptionsChangedListener =
+ argumentCaptor<SubscriptionManager.OnSubscriptionsChangedListener>()
+ .apply {
+ verify(subscriptionManager).addOnSubscriptionsChangedListener(any(), capture())
+ }
+ .lastValue
+
+ private fun KairosTestScope.getDemoModeCallback(): DemoMode =
+ argumentCaptor<DemoMode>()
+ .apply { verify(demoModeController).addCallback(capture()) }
+ .lastValue
+
+ companion object {
+ private const val SUB_1_ID = 1
+ private const val SUB_1_NAME = "Carrier $SUB_1_ID"
+ private val SUB_1: SubscriptionInfo = mock {
+ on { subscriptionId } doReturn SUB_1_ID
+ on { carrierName } doReturn SUB_1_NAME
+ on { profileClass } doReturn PROFILE_CLASS_UNSET
+ }
+ private val MODEL_1 =
+ SubscriptionModel(
+ subscriptionId = SUB_1_ID,
+ carrierName = SUB_1_NAME,
+ profileClass = PROFILE_CLASS_UNSET,
+ )
+
+ private const val SUB_2_ID = 2
+ private const val SUB_2_NAME = "Carrier $SUB_2_ID"
+ private val SUB_2: SubscriptionInfo = mock {
+ on { subscriptionId } doReturn SUB_2_ID
+ on { carrierName } doReturn SUB_2_NAME
+ on { profileClass } doReturn PROFILE_CLASS_UNSET
+ }
+ private val MODEL_2 =
+ SubscriptionModel(
+ subscriptionId = SUB_2_ID,
+ carrierName = SUB_2_NAME,
+ profileClass = PROFILE_CLASS_UNSET,
+ )
+ }
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/demo/DemoMobileConnectionKairosParameterizedTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/demo/DemoMobileConnectionKairosParameterizedTest.kt
new file mode 100644
index 000000000000..99cc93d6dc30
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/demo/DemoMobileConnectionKairosParameterizedTest.kt
@@ -0,0 +1,285 @@
+/*
+ * 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.data.repository.demo
+
+import android.telephony.Annotation
+import android.telephony.TelephonyManager
+import android.telephony.TelephonyManager.DATA_ACTIVITY_NONE
+import androidx.test.filters.SmallTest
+import com.android.settingslib.SignalIcon
+import com.android.settingslib.mobile.TelephonyIcons
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.kairos.ExperimentalKairosApi
+import com.android.systemui.kairos.KairosTestScope
+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.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.demo.model.FakeNetworkEventModel
+import com.android.systemui.statusbar.pipeline.mobile.data.repository.demoMobileConnectionsRepositoryKairos
+import com.android.systemui.statusbar.pipeline.mobile.data.repository.demoModeMobileConnectionDataSourceKairos
+import com.android.systemui.statusbar.pipeline.mobile.data.repository.fake
+import com.android.systemui.statusbar.pipeline.mobile.data.repository.wifiDataSource
+import com.android.systemui.statusbar.pipeline.shared.data.model.toMobileDataActivityModel
+import com.android.systemui.statusbar.pipeline.wifi.data.repository.demo.model.FakeWifiEventModel
+import com.android.systemui.testKosmos
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.flow.MutableStateFlow
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.kotlin.doReturn
+import org.mockito.kotlin.stub
+import platform.test.runner.parameterized.ParameterizedAndroidJunit4
+import platform.test.runner.parameterized.Parameters
+
+/**
+ * Parameterized test for all of the common values of [FakeNetworkEventModel]. This test simply
+ * verifies that passing the given model to [DemoMobileConnectionsRepositoryKairos] results in the
+ * correct flows emitting from the given connection.
+ */
+@OptIn(ExperimentalKairosApi::class)
+@SmallTest
+@RunWith(ParameterizedAndroidJunit4::class)
+internal class DemoMobileConnectionKairosParameterizedTest(private val testCase: TestCase) :
+ SysuiTestCase() {
+
+ private val Kosmos.fakeWifiEventFlow by Fixture { MutableStateFlow<FakeWifiEventModel?>(null) }
+
+ private val kosmos =
+ testKosmos().apply {
+ useUnconfinedTestDispatcher()
+ wifiDataSource.stub { on { wifiEvents } doReturn fakeWifiEventFlow }
+ }
+
+ private fun runTest(block: suspend KairosTestScope.() -> Unit) =
+ kosmos.run { runKairosTest { block() } }
+
+ @Test
+ fun demoNetworkData() = runTest {
+ val underTest by
+ demoMobileConnectionsRepositoryKairos.mobileConnectionsBySubId
+ .map { it[subId] }
+ .collectLastValue()
+ val networkModel =
+ FakeNetworkEventModel.Mobile(
+ level = testCase.level,
+ dataType = testCase.dataType,
+ subId = testCase.subId,
+ carrierId = testCase.carrierId,
+ inflateStrength = testCase.inflateStrength,
+ activity = testCase.activity,
+ carrierNetworkChange = testCase.carrierNetworkChange,
+ roaming = testCase.roaming,
+ name = "demo name",
+ slice = testCase.slice,
+ )
+ demoModeMobileConnectionDataSourceKairos.fake.mobileEvents.emit(networkModel)
+ assertConnection(underTest!!, networkModel)
+ }
+
+ private suspend fun KairosTestScope.assertConnection(
+ conn: DemoMobileConnectionRepositoryKairos,
+ model: FakeNetworkEventModel,
+ ) {
+ when (model) {
+ is FakeNetworkEventModel.Mobile -> {
+ kairos.transact {
+ assertThat(conn.subId).isEqualTo(model.subId)
+ assertThat(conn.cdmaLevel.sample()).isEqualTo(model.level)
+ assertThat(conn.primaryLevel.sample()).isEqualTo(model.level)
+ assertThat(conn.dataActivityDirection.sample())
+ .isEqualTo(
+ (model.activity ?: DATA_ACTIVITY_NONE).toMobileDataActivityModel()
+ )
+ assertThat(conn.carrierNetworkChangeActive.sample())
+ .isEqualTo(model.carrierNetworkChange)
+ assertThat(conn.isRoaming.sample()).isEqualTo(model.roaming)
+ assertThat(conn.networkName.sample())
+ .isEqualTo(NetworkNameModel.IntentDerived(model.name))
+ assertThat(conn.carrierName.sample())
+ .isEqualTo(
+ NetworkNameModel.SubscriptionDerived("${model.name} ${model.subId}")
+ )
+ assertThat(conn.hasPrioritizedNetworkCapabilities.sample())
+ .isEqualTo(model.slice)
+ assertThat(conn.isNonTerrestrial.sample()).isEqualTo(model.ntn)
+
+ // TODO(b/261029387): check these once we start handling them
+ assertThat(conn.isEmergencyOnly.sample()).isFalse()
+ assertThat(conn.isGsm.sample()).isFalse()
+ assertThat(conn.dataConnectionState.sample())
+ .isEqualTo(DataConnectionState.Connected)
+ }
+ }
+ // MobileDisabled isn't combinatorial in nature, and is tested in
+ // DemoMobileConnectionsRepositoryTest.kt
+ else -> {}
+ }
+ }
+
+ /** Matches [FakeNetworkEventModel] */
+ internal data class TestCase(
+ val level: Int,
+ val dataType: SignalIcon.MobileIconGroup,
+ val subId: Int,
+ val carrierId: Int,
+ val inflateStrength: Boolean,
+ @Annotation.DataActivityType val activity: Int,
+ val carrierNetworkChange: Boolean,
+ val roaming: Boolean,
+ val name: String,
+ val slice: Boolean,
+ val ntn: Boolean,
+ ) {
+ override fun toString(): String {
+ return "INPUT(level=$level, " +
+ "dataType=${dataType.name}, " +
+ "subId=$subId, " +
+ "carrierId=$carrierId, " +
+ "inflateStrength=$inflateStrength, " +
+ "activity=$activity, " +
+ "carrierNetworkChange=$carrierNetworkChange, " +
+ "roaming=$roaming, " +
+ "name=$name," +
+ "slice=$slice" +
+ "ntn=$ntn)"
+ }
+
+ // Convenience for iterating test data and creating new cases
+ fun modifiedBy(
+ level: Int? = null,
+ dataType: SignalIcon.MobileIconGroup? = null,
+ subId: Int? = null,
+ carrierId: Int? = null,
+ inflateStrength: Boolean? = null,
+ @Annotation.DataActivityType activity: Int? = null,
+ carrierNetworkChange: Boolean? = null,
+ roaming: Boolean? = null,
+ name: String? = null,
+ slice: Boolean? = null,
+ ntn: Boolean? = null,
+ ): TestCase =
+ TestCase(
+ level = level ?: this.level,
+ dataType = dataType ?: this.dataType,
+ subId = subId ?: this.subId,
+ carrierId = carrierId ?: this.carrierId,
+ inflateStrength = inflateStrength ?: this.inflateStrength,
+ activity = activity ?: this.activity,
+ carrierNetworkChange = carrierNetworkChange ?: this.carrierNetworkChange,
+ roaming = roaming ?: this.roaming,
+ name = name ?: this.name,
+ slice = slice ?: this.slice,
+ ntn = ntn ?: this.ntn,
+ )
+ }
+
+ companion object {
+ private val subId = 1
+
+ private val booleanList = listOf(true, false)
+ private val levels = listOf(0, 1, 2, 3)
+ private val dataTypes =
+ listOf(
+ TelephonyIcons.THREE_G,
+ TelephonyIcons.LTE,
+ TelephonyIcons.FOUR_G,
+ TelephonyIcons.NR_5G,
+ TelephonyIcons.NR_5G_PLUS,
+ )
+ private val carrierIds = listOf(1, 10, 100)
+ private val inflateStrength = booleanList
+ private val activity =
+ listOf(
+ TelephonyManager.DATA_ACTIVITY_NONE,
+ TelephonyManager.DATA_ACTIVITY_IN,
+ TelephonyManager.DATA_ACTIVITY_OUT,
+ TelephonyManager.DATA_ACTIVITY_INOUT,
+ )
+ private val carrierNetworkChange = booleanList
+ // false first so the base case doesn't have roaming set (more common)
+ private val roaming = listOf(false, true)
+ private val names = listOf("name 1", "name 2")
+ private val slice = listOf(false, true)
+ private val ntn = listOf(false, true)
+
+ @Parameters(name = "{0}") @JvmStatic fun data() = testData()
+
+ /**
+ * Generate some test data. For the sake of convenience, we'll parameterize only non-null
+ * network event data. So given the lists of test data:
+ * ```
+ * list1 = [1, 2, 3]
+ * list2 = [false, true]
+ * list3 = [a, b, c]
+ * ```
+ *
+ * We'll generate test cases for:
+ *
+ * Test (1, false, a) Test (2, false, a) Test (3, false, a) Test (1, true, a) Test (1,
+ * false, b) Test (1, false, c)
+ *
+ * NOTE: this is not a combinatorial product of all of the possible sets of parameters.
+ * Since this test is built to exercise demo mode, the general approach is to define a
+ * fully-formed "base case", and from there to make sure to use every valid parameter once,
+ * by defining the rest of the test cases against the base case. Specific use-cases can be
+ * added to the non-parameterized test, or manually below the generated test cases.
+ */
+ private fun testData(): List<TestCase> {
+ val testSet = mutableSetOf<TestCase>()
+
+ val baseCase =
+ TestCase(
+ levels.first(),
+ dataTypes.first(),
+ subId,
+ carrierIds.first(),
+ inflateStrength.first(),
+ activity.first(),
+ carrierNetworkChange.first(),
+ roaming.first(),
+ names.first(),
+ slice.first(),
+ ntn.first(),
+ )
+
+ val tail =
+ sequenceOf(
+ levels.map { baseCase.modifiedBy(level = it) },
+ dataTypes.map { baseCase.modifiedBy(dataType = it) },
+ carrierIds.map { baseCase.modifiedBy(carrierId = it) },
+ inflateStrength.map { baseCase.modifiedBy(inflateStrength = it) },
+ activity.map { baseCase.modifiedBy(activity = it) },
+ carrierNetworkChange.map { baseCase.modifiedBy(carrierNetworkChange = it) },
+ roaming.map { baseCase.modifiedBy(roaming = it) },
+ names.map { baseCase.modifiedBy(name = it) },
+ slice.map { baseCase.modifiedBy(slice = it) },
+ ntn.map { baseCase.modifiedBy(ntn = it) },
+ )
+ .flatten()
+
+ testSet.add(baseCase)
+ tail.toCollection(testSet)
+
+ return testSet.toList()
+ }
+ }
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/demo/DemoMobileConnectionsRepositoryKairosTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/demo/DemoMobileConnectionsRepositoryKairosTest.kt
new file mode 100644
index 000000000000..503d561a2234
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/demo/DemoMobileConnectionsRepositoryKairosTest.kt
@@ -0,0 +1,465 @@
+/*
+ * 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.data.repository.demo
+
+import android.telephony.TelephonyManager.DATA_ACTIVITY_INOUT
+import android.telephony.TelephonyManager.DATA_ACTIVITY_NONE
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.kairos.ExperimentalKairosApi
+import com.android.systemui.kairos.KairosTestScope
+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.useUnconfinedTestDispatcher
+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.demo.model.FakeNetworkEventModel
+import com.android.systemui.statusbar.pipeline.mobile.data.repository.demo.model.FakeNetworkEventModel.MobileDisabled
+import com.android.systemui.statusbar.pipeline.mobile.data.repository.demoMobileConnectionsRepositoryKairos
+import com.android.systemui.statusbar.pipeline.mobile.data.repository.demoModeMobileConnectionDataSourceKairos
+import com.android.systemui.statusbar.pipeline.mobile.data.repository.fake
+import com.android.systemui.statusbar.pipeline.mobile.data.repository.wifiDataSource
+import com.android.systemui.statusbar.pipeline.shared.data.model.toMobileDataActivityModel
+import com.android.systemui.statusbar.pipeline.wifi.data.repository.demo.model.FakeWifiEventModel
+import com.android.systemui.testKosmos
+import com.google.common.truth.Truth.assertThat
+import junit.framework.Assert
+import kotlinx.coroutines.flow.MutableStateFlow
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.kotlin.doReturn
+import org.mockito.kotlin.stub
+
+@OptIn(ExperimentalKairosApi::class)
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class DemoMobileConnectionsRepositoryKairosTest : SysuiTestCase() {
+
+ private val Kosmos.fakeWifiEventFlow by
+ Kosmos.Fixture { MutableStateFlow<FakeWifiEventModel?>(null) }
+
+ private val Kosmos.underTest
+ get() = demoMobileConnectionsRepositoryKairos
+
+ private val kosmos =
+ testKosmos().apply {
+ useUnconfinedTestDispatcher()
+ wifiDataSource.stub { on { wifiEvents } doReturn fakeWifiEventFlow }
+ }
+
+ private fun runTest(block: suspend KairosTestScope.() -> Unit) =
+ kosmos.run { runKairosTest { block() } }
+
+ @Test
+ fun isDefault_defaultsToTrue() = runTest {
+ underTest
+ val isDefault = kairos.transact { underTest.mobileIsDefault.sample() }
+ assertThat(isDefault).isTrue()
+ }
+
+ @Test
+ fun validated_defaultsToTrue() = runTest {
+ underTest
+ val isValidated = kairos.transact { underTest.defaultConnectionIsValidated.sample() }
+ assertThat(isValidated).isTrue()
+ }
+
+ @Test
+ fun networkEvent_createNewSubscription() = runTest {
+ val latest by underTest.subscriptions.collectLastValue()
+
+ assertThat(latest).isEmpty()
+
+ demoModeMobileConnectionDataSourceKairos.fake.mobileEvents.emit(validMobileEvent(subId = 1))
+
+ assertThat(latest).hasSize(1)
+ assertThat(latest!!.first().subscriptionId).isEqualTo(1)
+ }
+
+ @Test
+ fun wifiCarrierMergedEvent_createNewSubscription() = runTest {
+ val latest by underTest.subscriptions.collectLastValue()
+
+ assertThat(latest).isEmpty()
+
+ fakeWifiEventFlow.value = validCarrierMergedEvent(subId = 5)
+
+ assertThat(latest).hasSize(1)
+ assertThat(latest!!.first().subscriptionId).isEqualTo(5)
+ }
+
+ @Test
+ fun networkEvent_reusesSubscriptionWhenSameId() = runTest {
+ val latest by underTest.subscriptions.collectLastValue()
+
+ assertThat(latest).isEmpty()
+
+ demoModeMobileConnectionDataSourceKairos.fake.mobileEvents.emit(
+ validMobileEvent(subId = 1, level = 1)
+ )
+
+ assertThat(latest).hasSize(1)
+ assertThat(latest!!.first().subscriptionId).isEqualTo(1)
+
+ // Second network event comes in with the same subId, does not create a new subscription
+ demoModeMobileConnectionDataSourceKairos.fake.mobileEvents.emit(
+ validMobileEvent(subId = 1, level = 2)
+ )
+
+ assertThat(latest).hasSize(1)
+ assertThat(latest!!.first().subscriptionId).isEqualTo(1)
+ }
+
+ @Test
+ fun wifiCarrierMergedEvent_reusesSubscriptionWhenSameId() = runTest {
+ val latest by underTest.subscriptions.collectLastValue()
+
+ assertThat(latest).isEmpty()
+
+ fakeWifiEventFlow.value = validCarrierMergedEvent(subId = 5, level = 1)
+
+ assertThat(latest).hasSize(1)
+ assertThat(latest!!.first().subscriptionId).isEqualTo(5)
+
+ // Second network event comes in with the same subId, does not create a new subscription
+ fakeWifiEventFlow.value = validCarrierMergedEvent(subId = 5, level = 2)
+
+ assertThat(latest).hasSize(1)
+ assertThat(latest!!.first().subscriptionId).isEqualTo(5)
+ }
+
+ @Test
+ fun multipleSubscriptions() = runTest {
+ val latest by underTest.subscriptions.collectLastValue()
+
+ demoModeMobileConnectionDataSourceKairos.fake.mobileEvents.emit(validMobileEvent(subId = 1))
+ demoModeMobileConnectionDataSourceKairos.fake.mobileEvents.emit(validMobileEvent(subId = 2))
+
+ assertThat(latest).hasSize(2)
+ }
+
+ @Test
+ fun mobileSubscriptionAndCarrierMergedSubscription() = runTest {
+ val latest by underTest.subscriptions.collectLastValue()
+
+ demoModeMobileConnectionDataSourceKairos.fake.mobileEvents.emit(validMobileEvent(subId = 1))
+ fakeWifiEventFlow.value = validCarrierMergedEvent(subId = 5)
+
+ assertThat(latest).hasSize(2)
+ }
+
+ @Test
+ fun multipleMobileSubscriptionsAndCarrierMergedSubscription() = runTest {
+ val latest by underTest.subscriptions.collectLastValue()
+
+ demoModeMobileConnectionDataSourceKairos.fake.mobileEvents.emit(validMobileEvent(subId = 1))
+ demoModeMobileConnectionDataSourceKairos.fake.mobileEvents.emit(validMobileEvent(subId = 2))
+ fakeWifiEventFlow.value = validCarrierMergedEvent(subId = 3)
+
+ assertThat(latest).hasSize(3)
+ }
+
+ @Test
+ fun mobileDisabledEvent_disablesConnection_subIdSpecified_singleConn() = runTest {
+ val latest by underTest.subscriptions.collectLastValue()
+
+ demoModeMobileConnectionDataSourceKairos.fake.mobileEvents.emit(
+ validMobileEvent(subId = 1, level = 1)
+ )
+
+ demoModeMobileConnectionDataSourceKairos.fake.mobileEvents.emit(MobileDisabled(subId = 1))
+
+ assertThat(latest).hasSize(0)
+ }
+
+ @Test
+ fun mobileDisabledEvent_disablesConnection_subIdNotSpecified_singleConn() = runTest {
+ val latest by underTest.subscriptions.collectLastValue()
+
+ demoModeMobileConnectionDataSourceKairos.fake.mobileEvents.emit(
+ validMobileEvent(subId = 1, level = 1)
+ )
+
+ demoModeMobileConnectionDataSourceKairos.fake.mobileEvents.emit(
+ MobileDisabled(subId = null)
+ )
+
+ assertThat(latest).hasSize(0)
+ }
+
+ @Test
+ fun mobileDisabledEvent_disablesConnection_subIdSpecified_multipleConn() = runTest {
+ val latest by underTest.subscriptions.collectLastValue()
+
+ demoModeMobileConnectionDataSourceKairos.fake.mobileEvents.emit(
+ validMobileEvent(subId = 1, level = 1)
+ )
+
+ assertThat(latest).hasSize(1)
+
+ demoModeMobileConnectionDataSourceKairos.fake.mobileEvents.emit(
+ validMobileEvent(subId = 2, level = 1)
+ )
+
+ assertThat(latest).hasSize(2)
+
+ demoModeMobileConnectionDataSourceKairos.fake.mobileEvents.emit(MobileDisabled(subId = 2))
+
+ assertThat(latest).hasSize(1)
+ }
+
+ @Test
+ fun mobileDisabledEvent_subIdNotSpecified_multipleConn_ignoresCommand() = runTest {
+ val latest by underTest.subscriptions.collectLastValue()
+
+ demoModeMobileConnectionDataSourceKairos.fake.mobileEvents.emit(
+ validMobileEvent(subId = 1, level = 1)
+ )
+ demoModeMobileConnectionDataSourceKairos.fake.mobileEvents.emit(
+ validMobileEvent(subId = 2, level = 1)
+ )
+ demoModeMobileConnectionDataSourceKairos.fake.mobileEvents.emit(
+ MobileDisabled(subId = null)
+ )
+
+ assertThat(latest).hasSize(2)
+ }
+
+ @Test
+ fun wifiNetworkUpdatesToDisabled_carrierMergedConnectionRemoved() = runTest {
+ val latest by underTest.subscriptions.collectLastValue()
+
+ fakeWifiEventFlow.value = validCarrierMergedEvent(subId = 1)
+
+ assertThat(latest).hasSize(1)
+
+ fakeWifiEventFlow.value = FakeWifiEventModel.WifiDisabled
+
+ assertThat(latest).isEmpty()
+ }
+
+ @Test
+ fun wifiNetworkUpdatesToActive_carrierMergedConnectionRemoved() = runTest {
+ val latest by underTest.subscriptions.collectLastValue()
+
+ fakeWifiEventFlow.value = validCarrierMergedEvent(subId = 1)
+
+ assertThat(latest).hasSize(1)
+
+ fakeWifiEventFlow.value =
+ FakeWifiEventModel.Wifi(level = 1, activity = 0, ssid = null, validated = true)
+
+ assertThat(latest).isEmpty()
+ }
+
+ @Test
+ fun mobileSubUpdatesToCarrierMerged_onlyOneConnection() = runTest {
+ val latestSubsList by underTest.subscriptions.collectLastValue()
+ val connections by underTest.mobileConnectionsBySubId.map { it.values }.collectLastValue()
+
+ demoModeMobileConnectionDataSourceKairos.fake.mobileEvents.emit(
+ validMobileEvent(subId = 3, level = 2)
+ )
+ assertThat(latestSubsList).hasSize(1)
+
+ val carrierMergedEvent = validCarrierMergedEvent(subId = 3, level = 1)
+ fakeWifiEventFlow.value = carrierMergedEvent
+ assertThat(latestSubsList).hasSize(1)
+ val connection = connections!!.find { it.subId == 3 }!!
+ assertCarrierMergedConnection(connection, carrierMergedEvent)
+ }
+
+ @Test
+ fun mobileSubUpdatesToCarrierMergedThenBack_hasOldMobileData() = runTest {
+ val latestSubsList by underTest.subscriptions.collectLastValue()
+ val connections by underTest.mobileConnectionsBySubId.map { it.values }.collectLastValue()
+
+ val mobileEvent = validMobileEvent(subId = 3, level = 2)
+ demoModeMobileConnectionDataSourceKairos.fake.mobileEvents.emit(mobileEvent)
+ assertThat(latestSubsList).hasSize(1)
+
+ val carrierMergedEvent = validCarrierMergedEvent(subId = 3, level = 1)
+ fakeWifiEventFlow.value = carrierMergedEvent
+ assertThat(latestSubsList).hasSize(1)
+ var connection = connections!!.find { it.subId == 3 }!!
+ assertCarrierMergedConnection(connection, carrierMergedEvent)
+
+ // WHEN the carrier merged is removed
+ fakeWifiEventFlow.value =
+ FakeWifiEventModel.Wifi(level = 4, activity = 0, ssid = null, validated = true)
+
+ assertThat(latestSubsList).hasSize(1)
+ assertThat(connections).hasSize(1)
+
+ // THEN the subId=3 connection goes back to the mobile information
+ connection = connections!!.find { it.subId == 3 }!!
+ assertConnection(connection, mobileEvent)
+ }
+
+ @Test
+ fun demoConnection_singleSubscription() = runTest {
+ var currentEvent: FakeNetworkEventModel = validMobileEvent(subId = 1)
+ val connections by underTest.mobileConnectionsBySubId.map { it.values }.collectLastValue()
+
+ demoModeMobileConnectionDataSourceKairos.fake.mobileEvents.emit(currentEvent)
+
+ assertThat(connections).hasSize(1)
+ val connection1 = connections!!.first()
+
+ assertConnection(connection1, currentEvent)
+
+ // Exercise the whole api
+
+ currentEvent = validMobileEvent(subId = 1, level = 2)
+ demoModeMobileConnectionDataSourceKairos.fake.mobileEvents.emit(currentEvent)
+ assertConnection(connection1, currentEvent)
+ }
+
+ @Test
+ fun demoConnection_twoConnections_updateSecond_noAffectOnFirst() = runTest {
+ var currentEvent1 = validMobileEvent(subId = 1)
+ var connection1: DemoMobileConnectionRepositoryKairos? = null
+ var currentEvent2 = validMobileEvent(subId = 2)
+ var connection2: DemoMobileConnectionRepositoryKairos? = null
+ val connections by underTest.mobileConnectionsBySubId.map { it.values }.collectLastValue()
+
+ demoModeMobileConnectionDataSourceKairos.fake.mobileEvents.emit(currentEvent1)
+ demoModeMobileConnectionDataSourceKairos.fake.mobileEvents.emit(currentEvent2)
+ assertThat(connections).hasSize(2)
+ connections!!.forEach {
+ when (it.subId) {
+ 1 -> connection1 = it
+ 2 -> connection2 = it
+ else -> Assert.fail("Unexpected subscription")
+ }
+ }
+
+ assertConnection(connection1!!, currentEvent1)
+ assertConnection(connection2!!, currentEvent2)
+
+ // WHEN the event changes for connection 2, it updates, and connection 1 stays the same
+ currentEvent2 = validMobileEvent(subId = 2, activity = DATA_ACTIVITY_INOUT)
+ demoModeMobileConnectionDataSourceKairos.fake.mobileEvents.emit(currentEvent2)
+ assertConnection(connection1!!, currentEvent1)
+ assertConnection(connection2!!, currentEvent2)
+
+ // and vice versa
+ currentEvent1 = validMobileEvent(subId = 1, inflateStrength = true)
+ demoModeMobileConnectionDataSourceKairos.fake.mobileEvents.emit(currentEvent1)
+ assertConnection(connection1!!, currentEvent1)
+ assertConnection(connection2!!, currentEvent2)
+ }
+
+ @Test
+ fun demoConnection_twoConnections_updateCarrierMerged_noAffectOnFirst() = runTest {
+ var currentEvent1 = validMobileEvent(subId = 1)
+ var connection1: DemoMobileConnectionRepositoryKairos? = null
+ var currentEvent2 = validCarrierMergedEvent(subId = 2)
+ var connection2: DemoMobileConnectionRepositoryKairos? = null
+ val connections by underTest.mobileConnectionsBySubId.map { it.values }.collectLastValue()
+
+ demoModeMobileConnectionDataSourceKairos.fake.mobileEvents.emit(currentEvent1)
+ fakeWifiEventFlow.value = currentEvent2
+ assertThat(connections).hasSize(2)
+ connections!!.forEach {
+ when (it.subId) {
+ 1 -> connection1 = it
+ 2 -> connection2 = it
+ else -> Assert.fail("Unexpected subscription")
+ }
+ }
+
+ assertConnection(connection1!!, currentEvent1)
+ assertCarrierMergedConnection(connection2!!, currentEvent2)
+
+ // WHEN the event changes for connection 2, it updates, and connection 1 stays the same
+ currentEvent2 = validCarrierMergedEvent(subId = 2, level = 4)
+ fakeWifiEventFlow.value = currentEvent2
+ assertConnection(connection1!!, currentEvent1)
+ assertCarrierMergedConnection(connection2!!, currentEvent2)
+
+ // and vice versa
+ currentEvent1 = validMobileEvent(subId = 1, inflateStrength = true)
+ demoModeMobileConnectionDataSourceKairos.fake.mobileEvents.emit(currentEvent1)
+ assertConnection(connection1!!, currentEvent1)
+ assertCarrierMergedConnection(connection2!!, currentEvent2)
+ }
+
+ @Test
+ fun demoIsNotInEcmState() = runTest {
+ underTest
+ assertThat(kairos.transact { underTest.isInEcmMode.sample() }).isFalse()
+ }
+
+ private suspend fun KairosTestScope.assertConnection(
+ conn: DemoMobileConnectionRepositoryKairos,
+ model: FakeNetworkEventModel,
+ ) {
+ when (model) {
+ is FakeNetworkEventModel.Mobile -> {
+ kairos.transact {
+ assertThat(conn.subId).isEqualTo(model.subId)
+ assertThat(conn.cdmaLevel.sample()).isEqualTo(model.level)
+ assertThat(conn.primaryLevel.sample()).isEqualTo(model.level)
+ assertThat(conn.dataActivityDirection.sample())
+ .isEqualTo(
+ (model.activity ?: DATA_ACTIVITY_NONE).toMobileDataActivityModel()
+ )
+ assertThat(conn.carrierNetworkChangeActive.sample())
+ .isEqualTo(model.carrierNetworkChange)
+ assertThat(conn.isRoaming.sample()).isEqualTo(model.roaming)
+ assertThat(conn.networkName.sample())
+ .isEqualTo(NetworkNameModel.IntentDerived(model.name))
+ assertThat(conn.carrierName.sample())
+ .isEqualTo(
+ NetworkNameModel.SubscriptionDerived("${model.name} ${model.subId}")
+ )
+ assertThat(conn.hasPrioritizedNetworkCapabilities.sample())
+ .isEqualTo(model.slice)
+ assertThat(conn.isNonTerrestrial.sample()).isEqualTo(model.ntn)
+
+ // TODO(b/261029387) check these once we start handling them
+ assertThat(conn.isEmergencyOnly.sample()).isFalse()
+ assertThat(conn.isGsm.sample()).isFalse()
+ assertThat(conn.dataConnectionState.sample())
+ .isEqualTo(DataConnectionState.Connected)
+ }
+ }
+ else -> {}
+ }
+ }
+
+ private suspend fun KairosTestScope.assertCarrierMergedConnection(
+ conn: DemoMobileConnectionRepositoryKairos,
+ model: FakeWifiEventModel.CarrierMerged,
+ ) {
+ kairos.transact {
+ assertThat(conn.subId).isEqualTo(model.subscriptionId)
+ assertThat(conn.cdmaLevel.sample()).isEqualTo(model.level)
+ assertThat(conn.primaryLevel.sample()).isEqualTo(model.level)
+ assertThat(conn.carrierNetworkChangeActive.sample()).isEqualTo(false)
+ assertThat(conn.isRoaming.sample()).isEqualTo(false)
+ assertThat(conn.isEmergencyOnly.sample()).isFalse()
+ assertThat(conn.isGsm.sample()).isFalse()
+ assertThat(conn.dataConnectionState.sample()).isEqualTo(DataConnectionState.Connected)
+ assertThat(conn.hasPrioritizedNetworkCapabilities.sample()).isFalse()
+ }
+ }
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/CarrierMergedConnectionRepositoryKairosTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/CarrierMergedConnectionRepositoryKairosTest.kt
new file mode 100644
index 000000000000..1838d13b793a
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/CarrierMergedConnectionRepositoryKairosTest.kt
@@ -0,0 +1,244 @@
+/*
+ * 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.data.repository.prod
+
+import android.telephony.TelephonyManager
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.kairos.ActivatedKairosFixture
+import com.android.systemui.kairos.ExperimentalKairosApi
+import com.android.systemui.kairos.KairosTestScope
+import com.android.systemui.kairos.runKairosTest
+import com.android.systemui.kairos.stateOf
+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
+import com.android.systemui.statusbar.pipeline.shared.data.model.DataActivityModel
+import com.android.systemui.statusbar.pipeline.wifi.data.repository.fakeWifiRepository
+import com.android.systemui.statusbar.pipeline.wifi.data.repository.wifiRepository
+import com.android.systemui.statusbar.pipeline.wifi.shared.model.WifiNetworkModel
+import com.android.systemui.testKosmos
+import com.google.common.truth.Truth.assertThat
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.kotlin.doReturn
+import org.mockito.kotlin.mock
+import org.mockito.kotlin.stub
+
+@OptIn(ExperimentalKairosApi::class)
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class CarrierMergedConnectionRepositoryKairosTest : SysuiTestCase() {
+
+ private val Kosmos.underTest by ActivatedKairosFixture {
+ CarrierMergedConnectionRepositoryKairos(
+ subId = SUB_ID,
+ tableLogBuffer = logcatTableLogBuffer(this),
+ telephonyManager = telephonyManager,
+ wifiRepository = wifiRepository,
+ isInEcmMode = stateOf(false),
+ )
+ }
+
+ private val Kosmos.telephonyManager: TelephonyManager by Fixture {
+ mock {
+ on { subscriptionId } doReturn SUB_ID
+ on { simOperatorName } doReturn ""
+ }
+ }
+
+ private fun runTest(block: suspend KairosTestScope.() -> Unit) =
+ testKosmos().run {
+ useUnconfinedTestDispatcher()
+ runKairosTest { block() }
+ }
+
+ @Test
+ fun inactiveWifi_isDefault() = runTest {
+ val latestConnState by underTest.dataConnectionState.collectLastValue()
+ val latestNetType by underTest.resolvedNetworkType.collectLastValue()
+
+ fakeWifiRepository.setWifiNetwork(WifiNetworkModel.Inactive())
+
+ assertThat(latestConnState).isEqualTo(DataConnectionState.Disconnected)
+ assertThat(latestNetType).isNotEqualTo(ResolvedNetworkType.CarrierMergedNetworkType)
+ }
+
+ @Test
+ fun activeWifi_isDefault() = runTest {
+ val latestConnState by underTest.dataConnectionState.collectLastValue()
+ val latestNetType by underTest.resolvedNetworkType.collectLastValue()
+
+ fakeWifiRepository.setWifiNetwork(WifiNetworkModel.Active.of(level = 1))
+
+ assertThat(latestConnState).isEqualTo(DataConnectionState.Disconnected)
+ assertThat(latestNetType).isNotEqualTo(ResolvedNetworkType.CarrierMergedNetworkType)
+ }
+
+ @Test
+ fun carrierMergedWifi_isValidAndFieldsComeFromWifiNetwork() = runTest {
+ val latest by underTest.primaryLevel.collectLastValue()
+
+ fakeWifiRepository.setIsWifiEnabled(true)
+ fakeWifiRepository.setIsWifiDefault(true)
+
+ fakeWifiRepository.setWifiNetwork(
+ WifiNetworkModel.CarrierMerged.of(subscriptionId = SUB_ID, level = 3)
+ )
+
+ assertThat(latest).isEqualTo(3)
+ }
+
+ @Test
+ fun activity_comesFromWifiActivity() = runTest {
+ val latest by underTest.dataActivityDirection.collectLastValue()
+
+ fakeWifiRepository.setIsWifiEnabled(true)
+ fakeWifiRepository.setIsWifiDefault(true)
+ fakeWifiRepository.setWifiNetwork(
+ WifiNetworkModel.CarrierMerged.of(subscriptionId = SUB_ID, level = 3)
+ )
+ fakeWifiRepository.setWifiActivity(
+ DataActivityModel(hasActivityIn = true, hasActivityOut = false)
+ )
+
+ assertThat(latest!!.hasActivityIn).isTrue()
+ assertThat(latest!!.hasActivityOut).isFalse()
+
+ fakeWifiRepository.setWifiActivity(
+ DataActivityModel(hasActivityIn = false, hasActivityOut = true)
+ )
+
+ assertThat(latest!!.hasActivityIn).isFalse()
+ assertThat(latest!!.hasActivityOut).isTrue()
+ }
+
+ @Test
+ fun carrierMergedWifi_wrongSubId_isDefault() = runTest {
+ val latestLevel by underTest.primaryLevel.collectLastValue()
+ val latestType by underTest.resolvedNetworkType.collectLastValue()
+
+ fakeWifiRepository.setWifiNetwork(
+ WifiNetworkModel.CarrierMerged.of(subscriptionId = SUB_ID + 10, level = 3)
+ )
+
+ assertThat(latestLevel).isNotEqualTo(3)
+ assertThat(latestType).isNotEqualTo(ResolvedNetworkType.CarrierMergedNetworkType)
+ }
+
+ // This scenario likely isn't possible, but write a test for it anyway
+ @Test
+ fun carrierMergedButNotEnabled_isDefault() = runTest {
+ val latest by underTest.primaryLevel.collectLastValue()
+
+ fakeWifiRepository.setWifiNetwork(
+ WifiNetworkModel.CarrierMerged.of(subscriptionId = SUB_ID, level = 3)
+ )
+ fakeWifiRepository.setIsWifiEnabled(false)
+
+ assertThat(latest).isNotEqualTo(3)
+ }
+
+ // This scenario likely isn't possible, but write a test for it anyway
+ @Test
+ fun carrierMergedButWifiNotDefault_isDefault() = runTest {
+ val latest by underTest.primaryLevel.collectLastValue()
+
+ fakeWifiRepository.setWifiNetwork(
+ WifiNetworkModel.CarrierMerged.of(subscriptionId = SUB_ID, level = 3)
+ )
+ fakeWifiRepository.setIsWifiDefault(false)
+
+ assertThat(latest).isNotEqualTo(3)
+ }
+
+ @Test
+ fun numberOfLevels_comesFromCarrierMerged() = runTest {
+ val latest by underTest.numberOfLevels.collectLastValue()
+
+ fakeWifiRepository.setWifiNetwork(
+ WifiNetworkModel.CarrierMerged.of(
+ subscriptionId = SUB_ID,
+ level = 1,
+ numberOfLevels = 6,
+ )
+ )
+
+ assertThat(latest).isEqualTo(6)
+ }
+
+ @Test
+ fun dataEnabled_matchesWifiEnabled() = runTest {
+ val latest by underTest.dataEnabled.collectLastValue()
+
+ fakeWifiRepository.setIsWifiEnabled(true)
+ assertThat(latest).isTrue()
+
+ fakeWifiRepository.setIsWifiEnabled(false)
+ assertThat(latest).isFalse()
+ }
+
+ @Test
+ fun cdmaRoaming_alwaysFalse() = runTest {
+ val latest by underTest.cdmaRoaming.collectLastValue()
+ assertThat(latest).isFalse()
+ }
+
+ @Test
+ fun networkName_usesSimOperatorNameAsInitial() = runTest {
+ telephonyManager.stub { on { simOperatorName } doReturn "Test SIM name" }
+
+ val latest by underTest.networkName.collectLastValue()
+
+ assertThat(latest).isEqualTo(NetworkNameModel.SimDerived("Test SIM name"))
+ }
+
+ @Test
+ fun networkName_updatesOnNetworkUpdate() = runTest {
+ fakeWifiRepository.setIsWifiEnabled(true)
+ fakeWifiRepository.setIsWifiDefault(true)
+
+ telephonyManager.stub { on { simOperatorName } doReturn "Test SIM name" }
+
+ val latest by underTest.networkName.collectLastValue()
+
+ assertThat(latest).isEqualTo(NetworkNameModel.SimDerived("Test SIM name"))
+
+ telephonyManager.stub { on { simOperatorName } doReturn "New SIM name" }
+ fakeWifiRepository.setWifiNetwork(
+ WifiNetworkModel.CarrierMerged.of(subscriptionId = SUB_ID, level = 3)
+ )
+
+ assertThat(latest).isEqualTo(NetworkNameModel.SimDerived("New SIM name"))
+ }
+
+ @Test
+ fun isAllowedDuringAirplaneMode_alwaysTrue() = runTest {
+ val latest by underTest.isAllowedDuringAirplaneMode.collectLastValue()
+
+ assertThat(latest).isTrue()
+ }
+
+ private companion object {
+ const val SUB_ID = 123
+ }
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/FullMobileConnectionRepositoryKairosTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/FullMobileConnectionRepositoryKairosTest.kt
new file mode 100644
index 000000000000..858bb095df93
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/FullMobileConnectionRepositoryKairosTest.kt
@@ -0,0 +1,544 @@
+/*
+ * 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.data.repository.prod
+
+import android.os.PersistableBundle
+import android.telephony.ServiceState
+import android.telephony.SignalStrength
+import android.telephony.SubscriptionManager.PROFILE_CLASS_UNSET
+import android.telephony.TelephonyCallback
+import android.telephony.TelephonyManager
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.activated
+import com.android.systemui.flags.Flags.ROAMING_INDICATOR_VIA_DISPLAY_INFO
+import com.android.systemui.flags.fakeFeatureFlagsClassic
+import com.android.systemui.flags.featureFlagsClassic
+import com.android.systemui.kairos.ActivatedKairosFixture
+import com.android.systemui.kairos.BuildSpec
+import com.android.systemui.kairos.ExperimentalKairosApi
+import com.android.systemui.kairos.KairosTestScope
+import com.android.systemui.kairos.MutableState
+import com.android.systemui.kairos.buildSpec
+import com.android.systemui.kairos.kairos
+import com.android.systemui.kairos.runKairosTest
+import com.android.systemui.kairos.stateOf
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.Kosmos.Fixture
+import com.android.systemui.kosmos.testDispatcher
+import com.android.systemui.kosmos.useUnconfinedTestDispatcher
+import com.android.systemui.log.table.logcatTableLogBuffer
+import com.android.systemui.statusbar.pipeline.mobile.data.model.NetworkNameModel
+import com.android.systemui.statusbar.pipeline.mobile.data.model.SubscriptionModel
+import com.android.systemui.statusbar.pipeline.mobile.data.model.SystemUiCarrierConfig
+import com.android.systemui.statusbar.pipeline.mobile.data.repository.FakeMobileConnectionRepositoryKairos
+import com.android.systemui.statusbar.pipeline.mobile.data.repository.MobileConnectionRepositoryKairos
+import com.android.systemui.statusbar.pipeline.mobile.data.repository.prod.FullMobileConnectionRepository.Companion.COL_EMERGENCY
+import com.android.systemui.statusbar.pipeline.mobile.data.repository.prod.FullMobileConnectionRepository.Companion.COL_OPERATOR
+import com.android.systemui.statusbar.pipeline.mobile.data.repository.prod.FullMobileConnectionRepositoryKairos.Companion.COL_PRIMARY_LEVEL
+import com.android.systemui.statusbar.pipeline.mobile.data.repository.prod.MobileTelephonyHelpers.getTelephonyCallbackForType
+import com.android.systemui.statusbar.pipeline.wifi.data.repository.fakeWifiRepository
+import com.android.systemui.statusbar.pipeline.wifi.data.repository.wifiRepository
+import com.android.systemui.statusbar.pipeline.wifi.shared.model.WifiNetworkModel
+import com.android.systemui.testKosmos
+import com.google.common.truth.Truth.assertThat
+import java.io.PrintWriter
+import java.io.StringWriter
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Mockito.verify
+import org.mockito.kotlin.any
+import org.mockito.kotlin.argumentCaptor
+import org.mockito.kotlin.doReturn
+import org.mockito.kotlin.mock
+
+/**
+ * This repo acts as a dispatcher to either the `typical` or `carrier merged` versions of the
+ * repository interface it's switching on. These tests just need to verify that the entire interface
+ * properly switches over when the value of `isCarrierMerged` changes.
+ */
+@OptIn(ExperimentalKairosApi::class)
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class FullMobileConnectionRepositoryKairosTest : SysuiTestCase() {
+ private val Kosmos.fakeMobileRepo by Fixture {
+ FakeMobileConnectionRepositoryKairos(SUB_ID, kairos, mobileLogger)
+ }
+
+ private val Kosmos.fakeCarrierMergedRepo by Fixture {
+ FakeMobileConnectionRepositoryKairos(SUB_ID, kairos, mobileLogger).apply {
+ // Mimicks the real carrier merged repository
+ isAllowedDuringAirplaneMode.setValue(true)
+ }
+ }
+
+ private var Kosmos.mobileRepo: MobileConnectionRepositoryKairos by Fixture { fakeMobileRepo }
+ private var Kosmos.carrierMergedRepoSpec:
+ BuildSpec<MobileConnectionRepositoryKairos> by Fixture {
+ buildSpec { fakeCarrierMergedRepo }
+ }
+
+ private val Kosmos.mobileLogger by Fixture { logcatTableLogBuffer(this, "TestName") }
+
+ private val Kosmos.underTest by ActivatedKairosFixture {
+ FullMobileConnectionRepositoryKairos(
+ SUB_ID,
+ mobileLogger,
+ mobileRepo,
+ carrierMergedRepoSpec,
+ isCarrierMerged,
+ )
+ }
+
+ private val Kosmos.subscriptionModel by Fixture {
+ MutableState(
+ kairos,
+ SubscriptionModel(
+ subscriptionId = SUB_ID,
+ carrierName = DEFAULT_NAME,
+ profileClass = PROFILE_CLASS_UNSET,
+ ),
+ )
+ }
+
+ private val Kosmos.isCarrierMerged by Fixture { MutableState(kairos, false) }
+
+ // Use a real config, with no overrides
+ private val systemUiCarrierConfig = SystemUiCarrierConfig(SUB_ID, PersistableBundle())
+
+ private val kosmos =
+ testKosmos().apply {
+ useUnconfinedTestDispatcher()
+ fakeFeatureFlagsClassic.set(ROAMING_INDICATOR_VIA_DISPLAY_INFO, true)
+ }
+
+ private fun runTest(block: suspend KairosTestScope.() -> Unit) =
+ kosmos.run { runKairosTest { block() } }
+
+ @Test
+ fun startingIsCarrierMerged_usesCarrierMergedInitially() = runTest {
+ val carrierMergedOperatorName = "Carrier Merged Operator"
+ val nonCarrierMergedName = "Non-carrier-merged"
+
+ fakeCarrierMergedRepo.operatorAlphaShort.setValue(carrierMergedOperatorName)
+ fakeMobileRepo.operatorAlphaShort.setValue(nonCarrierMergedName)
+
+ isCarrierMerged.setValue(true)
+
+ val activeRepo by underTest.activeRepo.collectLastValue()
+ val operatorAlphaShort by underTest.operatorAlphaShort.collectLastValue()
+
+ assertThat(activeRepo).isEqualTo(fakeCarrierMergedRepo)
+ assertThat(operatorAlphaShort).isEqualTo(carrierMergedOperatorName)
+ }
+
+ @Test
+ fun startingNotCarrierMerged_usesTypicalInitially() = runTest {
+ val carrierMergedOperatorName = "Carrier Merged Operator"
+ val nonCarrierMergedName = "Typical Operator"
+
+ fakeCarrierMergedRepo.operatorAlphaShort.setValue(carrierMergedOperatorName)
+ fakeMobileRepo.operatorAlphaShort.setValue(nonCarrierMergedName)
+ isCarrierMerged.setValue(false)
+
+ assertThat(underTest.activeRepo.collectLastValue().value).isEqualTo(fakeMobileRepo)
+ assertThat(underTest.operatorAlphaShort.collectLastValue().value)
+ .isEqualTo(nonCarrierMergedName)
+ }
+
+ @Test
+ fun activeRepo_matchesIsCarrierMerged() = runTest {
+ isCarrierMerged.setValue(false)
+
+ val latest by underTest.activeRepo.collectLastValue()
+
+ isCarrierMerged.setValue(true)
+
+ assertThat(latest).isEqualTo(fakeCarrierMergedRepo)
+
+ isCarrierMerged.setValue(false)
+
+ assertThat(latest).isEqualTo(fakeMobileRepo)
+
+ isCarrierMerged.setValue(true)
+
+ assertThat(latest).isEqualTo(fakeCarrierMergedRepo)
+ }
+
+ @Test
+ fun connectionInfo_getsUpdatesFromRepo_carrierMerged() = runTest {
+ isCarrierMerged.setValue(false)
+
+ val latestName by underTest.operatorAlphaShort.collectLastValue()
+ val latestLevel by underTest.primaryLevel.collectLastValue()
+
+ isCarrierMerged.setValue(true)
+
+ val operator1 = "Carrier Merged Operator"
+ val level1 = 1
+ fakeCarrierMergedRepo.operatorAlphaShort.setValue(operator1)
+ fakeCarrierMergedRepo.primaryLevel.setValue(level1)
+
+ assertThat(latestName).isEqualTo(operator1)
+ assertThat(latestLevel).isEqualTo(level1)
+
+ val operator2 = "Carrier Merged Operator #2"
+ val level2 = 2
+ fakeCarrierMergedRepo.operatorAlphaShort.setValue(operator2)
+ fakeCarrierMergedRepo.primaryLevel.setValue(level2)
+
+ assertThat(latestName).isEqualTo(operator2)
+ assertThat(latestLevel).isEqualTo(level2)
+
+ val operator3 = "Carrier Merged Operator #3"
+ val level3 = 3
+ fakeCarrierMergedRepo.operatorAlphaShort.setValue(operator3)
+ fakeCarrierMergedRepo.primaryLevel.setValue(level3)
+
+ assertThat(latestName).isEqualTo(operator3)
+ assertThat(latestLevel).isEqualTo(level3)
+ }
+
+ @Test
+ fun connectionInfo_getsUpdatesFromRepo_mobile() = runTest {
+ isCarrierMerged.setValue(false)
+
+ val latestName by underTest.operatorAlphaShort.collectLastValue()
+ val latestLevel by underTest.primaryLevel.collectLastValue()
+
+ isCarrierMerged.setValue(false)
+
+ val operator1 = "Typical Merged Operator"
+ val level1 = 1
+ fakeMobileRepo.operatorAlphaShort.setValue(operator1)
+ fakeMobileRepo.primaryLevel.setValue(level1)
+
+ assertThat(latestName).isEqualTo(operator1)
+ assertThat(latestLevel).isEqualTo(level1)
+
+ val operator2 = "Typical Merged Operator #2"
+ val level2 = 2
+ fakeMobileRepo.operatorAlphaShort.setValue(operator2)
+ fakeMobileRepo.primaryLevel.setValue(level2)
+
+ assertThat(latestName).isEqualTo(operator2)
+ assertThat(latestLevel).isEqualTo(level2)
+
+ val operator3 = "Typical Merged Operator #3"
+ val level3 = 3
+ fakeMobileRepo.operatorAlphaShort.setValue(operator3)
+ fakeMobileRepo.primaryLevel.setValue(level3)
+
+ assertThat(latestName).isEqualTo(operator3)
+ assertThat(latestLevel).isEqualTo(level3)
+ }
+
+ @Test
+ fun connectionInfo_updatesWhenCarrierMergedUpdates() = runTest {
+ isCarrierMerged.setValue(false)
+
+ val latestName by underTest.operatorAlphaShort.collectLastValue()
+ val latestLevel by underTest.primaryLevel.collectLastValue()
+
+ val carrierMergedOperator = "Carrier Merged Operator"
+ val carrierMergedLevel = 4
+ fakeCarrierMergedRepo.operatorAlphaShort.setValue(carrierMergedOperator)
+ fakeCarrierMergedRepo.primaryLevel.setValue(carrierMergedLevel)
+
+ val mobileName = "Typical Operator"
+ val mobileLevel = 2
+ fakeMobileRepo.operatorAlphaShort.setValue(mobileName)
+ fakeMobileRepo.primaryLevel.setValue(mobileLevel)
+
+ // Start with the mobile info
+ assertThat(latestName).isEqualTo(mobileName)
+ assertThat(latestLevel).isEqualTo(mobileLevel)
+
+ // WHEN isCarrierMerged is set to true
+ isCarrierMerged.setValue(true)
+
+ // THEN the carrier merged info is used
+ assertThat(latestName).isEqualTo(carrierMergedOperator)
+ assertThat(latestLevel).isEqualTo(carrierMergedLevel)
+
+ val newCarrierMergedName = "New CM Operator"
+ val newCarrierMergedLevel = 0
+ fakeCarrierMergedRepo.operatorAlphaShort.setValue(newCarrierMergedName)
+ fakeCarrierMergedRepo.primaryLevel.setValue(newCarrierMergedLevel)
+
+ assertThat(latestName).isEqualTo(newCarrierMergedName)
+ assertThat(latestLevel).isEqualTo(newCarrierMergedLevel)
+
+ // WHEN isCarrierMerged is set to false
+ isCarrierMerged.setValue(false)
+
+ // THEN the typical info is used
+ assertThat(latestName).isEqualTo(mobileName)
+ assertThat(latestLevel).isEqualTo(mobileLevel)
+
+ val newMobileName = "New MobileOperator"
+ val newMobileLevel = 3
+ fakeMobileRepo.operatorAlphaShort.setValue(newMobileName)
+ fakeMobileRepo.primaryLevel.setValue(newMobileLevel)
+
+ assertThat(latestName).isEqualTo(newMobileName)
+ assertThat(latestLevel).isEqualTo(newMobileLevel)
+ }
+
+ @Test
+ fun isAllowedDuringAirplaneMode_updatesWhenCarrierMergedUpdates() = runTest {
+ isCarrierMerged.setValue(false)
+
+ val latest by underTest.isAllowedDuringAirplaneMode.collectLastValue()
+
+ assertThat(latest).isFalse()
+
+ isCarrierMerged.setValue(true)
+
+ assertThat(latest).isTrue()
+
+ isCarrierMerged.setValue(false)
+
+ assertThat(latest).isFalse()
+ }
+
+ @Test
+ fun connectionInfo_logging_notCarrierMerged_getsUpdates() = runTest {
+ // SETUP: Use real repositories to verify the diffing still works. (See b/267501739.)
+ val telephonyManager: TelephonyManager = mock {
+ on { simOperatorName } doReturn ""
+ on { subscriptionId } doReturn SUB_ID
+ }
+ fakeWifiRepository.setIsWifiEnabled(true)
+ fakeWifiRepository.setIsWifiDefault(true)
+ mobileRepo = createRealMobileRepo(telephonyManager)
+ carrierMergedRepoSpec = realCarrierMergedRepo(telephonyManager)
+
+ isCarrierMerged.setValue(false)
+
+ // Stand-up activated repository
+ underTest
+
+ // WHEN we set up some mobile connection info
+ val serviceState = ServiceState()
+ serviceState.setOperatorName("longName", "OpTypical", "1")
+ serviceState.isEmergencyOnly = true
+ getTelephonyCallbackForType<TelephonyCallback.ServiceStateListener>(telephonyManager)
+ .onServiceStateChanged(serviceState)
+
+ // THEN it's logged to the buffer
+ assertThat(dumpBuffer()).contains("$COL_OPERATOR${BUFFER_SEPARATOR}OpTypical")
+ assertThat(dumpBuffer()).contains("$COL_EMERGENCY${BUFFER_SEPARATOR}true")
+
+ // WHEN we update mobile connection info
+ val serviceState2 = ServiceState()
+ serviceState2.setOperatorName("longName", "OpDiff", "1")
+ serviceState2.isEmergencyOnly = false
+ getTelephonyCallbackForType<TelephonyCallback.ServiceStateListener>(telephonyManager)
+ .onServiceStateChanged(serviceState2)
+
+ // THEN the updates are logged
+ assertThat(dumpBuffer()).contains("$COL_OPERATOR${BUFFER_SEPARATOR}OpDiff")
+ assertThat(dumpBuffer()).contains("$COL_EMERGENCY${BUFFER_SEPARATOR}false")
+ }
+
+ @Test
+ fun connectionInfo_logging_carrierMerged_getsUpdates() = runTest {
+ // SETUP: Use real repositories to verify the diffing still works. (See b/267501739.)
+ val telephonyManager: TelephonyManager = mock {
+ on { simOperatorName } doReturn ""
+ on { subscriptionId } doReturn SUB_ID
+ }
+ fakeWifiRepository.setIsWifiEnabled(true)
+ fakeWifiRepository.setIsWifiDefault(true)
+ mobileRepo = createRealMobileRepo(telephonyManager)
+ carrierMergedRepoSpec = realCarrierMergedRepo(telephonyManager)
+
+ isCarrierMerged.setValue(true)
+
+ // Stand-up activated repository
+ underTest
+
+ // WHEN we set up carrier merged info
+ fakeWifiRepository.setWifiNetwork(WifiNetworkModel.CarrierMerged.of(SUB_ID, level = 3))
+
+ // THEN the carrier merged info is logged
+ assertThat(dumpBuffer()).contains("$COL_PRIMARY_LEVEL${BUFFER_SEPARATOR}3")
+
+ // WHEN we update the info
+ fakeWifiRepository.setWifiNetwork(WifiNetworkModel.CarrierMerged.of(SUB_ID, level = 1))
+
+ // THEN the updates are logged
+ assertThat(dumpBuffer()).contains("$COL_PRIMARY_LEVEL${BUFFER_SEPARATOR}1")
+ }
+
+ @Test
+ fun connectionInfo_logging_updatesWhenCarrierMergedUpdates() = runTest {
+ // SETUP: Use real repositories to verify the diffing still works. (See b/267501739.)
+ val telephonyManager: TelephonyManager = mock {
+ on { simOperatorName } doReturn ""
+ on { subscriptionId } doReturn SUB_ID
+ }
+ fakeWifiRepository.setIsWifiEnabled(true)
+ fakeWifiRepository.setIsWifiDefault(true)
+ mobileRepo = createRealMobileRepo(telephonyManager)
+ carrierMergedRepoSpec = realCarrierMergedRepo(telephonyManager)
+
+ isCarrierMerged.setValue(false)
+
+ // Stand-up activated repository
+ underTest
+
+ // WHEN we set up some mobile connection info
+ val cb =
+ getTelephonyCallbackForType<TelephonyCallback.SignalStrengthsListener>(telephonyManager)
+ cb.onSignalStrengthsChanged(mock(stubOnly = true) { on { level } doReturn 1 })
+
+ // THEN it's logged to the buffer
+ assertThat(dumpBuffer()).contains("$COL_PRIMARY_LEVEL${BUFFER_SEPARATOR}1")
+
+ // WHEN isCarrierMerged is set to true
+ fakeWifiRepository.setWifiNetwork(WifiNetworkModel.CarrierMerged.of(SUB_ID, level = 3))
+ isCarrierMerged.setValue(true)
+
+ // THEN the carrier merged info is logged
+ assertThat(dumpBuffer()).contains("$COL_PRIMARY_LEVEL${BUFFER_SEPARATOR}3")
+
+ // WHEN the carrier merge network is updated
+ fakeWifiRepository.setWifiNetwork(WifiNetworkModel.CarrierMerged.of(SUB_ID, level = 4))
+
+ // THEN the new level is logged
+ assertThat(dumpBuffer()).contains("$COL_PRIMARY_LEVEL${BUFFER_SEPARATOR}4")
+
+ // WHEN isCarrierMerged is set to false
+ isCarrierMerged.setValue(false)
+
+ // THEN the typical info is logged
+ // Note: Since our first logs also had the typical info, we need to search the log
+ // contents for after our carrier merged level log.
+ val fullBuffer = dumpBuffer()
+ val carrierMergedContentIndex = fullBuffer.indexOf("${BUFFER_SEPARATOR}4")
+ val bufferAfterCarrierMerged = fullBuffer.substring(carrierMergedContentIndex)
+ assertThat(bufferAfterCarrierMerged).contains("$COL_PRIMARY_LEVEL${BUFFER_SEPARATOR}1")
+
+ // WHEN the normal network is updated
+ cb.onSignalStrengthsChanged(mock(stubOnly = true) { on { level } doReturn 0 })
+
+ // THEN the new level is logged
+ assertThat(dumpBuffer()).contains("$COL_PRIMARY_LEVEL${BUFFER_SEPARATOR}0")
+ }
+
+ @Test
+ fun connectionInfo_logging_doesNotLogUpdatesForNotActiveRepo() = runTest {
+ // SETUP: Use real repositories to verify the diffing still works. (See b/267501739.)
+ val telephonyManager: TelephonyManager = mock {
+ on { simOperatorName } doReturn ""
+ on { subscriptionId } doReturn SUB_ID
+ }
+ fakeWifiRepository.setIsWifiEnabled(true)
+ fakeWifiRepository.setIsWifiDefault(true)
+ mobileRepo = createRealMobileRepo(telephonyManager)
+ carrierMergedRepoSpec = realCarrierMergedRepo(telephonyManager)
+
+ // WHEN isCarrierMerged = false
+ isCarrierMerged.setValue(false)
+
+ // Stand-up activated repository
+ underTest
+
+ fun setSignalLevel(newLevel: Int) {
+ val signalStrength =
+ mock<SignalStrength>(stubOnly = true) { on { level } doReturn newLevel }
+ argumentCaptor<TelephonyCallback>()
+ .apply { verify(telephonyManager).registerTelephonyCallback(any(), capture()) }
+ .allValues
+ .asSequence()
+ .filterIsInstance<TelephonyCallback.SignalStrengthsListener>()
+ .forEach { it.onSignalStrengthsChanged(signalStrength) }
+ }
+
+ // WHEN we set up some mobile connection info
+ setSignalLevel(1)
+
+ // THEN updates to the carrier merged level aren't logged
+ fakeWifiRepository.setWifiNetwork(WifiNetworkModel.CarrierMerged.of(SUB_ID, level = 4))
+ assertThat(dumpBuffer()).doesNotContain("$COL_PRIMARY_LEVEL${BUFFER_SEPARATOR}4")
+
+ fakeWifiRepository.setWifiNetwork(WifiNetworkModel.CarrierMerged.of(SUB_ID, level = 3))
+ assertThat(dumpBuffer()).doesNotContain("$COL_PRIMARY_LEVEL${BUFFER_SEPARATOR}3")
+
+ // WHEN isCarrierMerged is set to true
+ isCarrierMerged.setValue(true)
+
+ // THEN updates to the normal level aren't logged
+ setSignalLevel(5)
+ assertThat(dumpBuffer()).doesNotContain("$COL_PRIMARY_LEVEL${BUFFER_SEPARATOR}5")
+
+ setSignalLevel(6)
+ assertThat(dumpBuffer()).doesNotContain("$COL_PRIMARY_LEVEL${BUFFER_SEPARATOR}6")
+ }
+
+ private fun KairosTestScope.createRealMobileRepo(
+ telephonyManager: TelephonyManager
+ ): MobileConnectionRepositoryKairosImpl =
+ MobileConnectionRepositoryKairosImpl(
+ subId = SUB_ID,
+ context = context,
+ subscriptionModel = subscriptionModel,
+ defaultNetworkName = DEFAULT_NAME_MODEL,
+ networkNameSeparator = SEP,
+ connectivityManager = mock(stubOnly = true),
+ telephonyManager = telephonyManager,
+ systemUiCarrierConfig = systemUiCarrierConfig,
+ broadcastDispatcher = fakeBroadcastDispatcher,
+ mobileMappingsProxy = mock(stubOnly = true),
+ bgDispatcher = testDispatcher,
+ logger = mock(stubOnly = true),
+ tableLogBuffer = mobileLogger,
+ flags = featureFlagsClassic,
+ )
+ .activated()
+
+ private fun Kosmos.realCarrierMergedRepo(
+ telephonyManager: TelephonyManager
+ ): BuildSpec<CarrierMergedConnectionRepositoryKairos> = buildSpec {
+ activated {
+ CarrierMergedConnectionRepositoryKairos(
+ subId = SUB_ID,
+ tableLogBuffer = mobileLogger,
+ telephonyManager = telephonyManager,
+ wifiRepository = wifiRepository,
+ isInEcmMode = stateOf(false),
+ )
+ }
+ }
+
+ private fun Kosmos.dumpBuffer(): String {
+ val outputWriter = StringWriter()
+ mobileLogger.dump(PrintWriter(outputWriter), arrayOf())
+ return outputWriter.toString()
+ }
+
+ private companion object {
+ const val SUB_ID = 42
+ private const val DEFAULT_NAME = "default name"
+ private val DEFAULT_NAME_MODEL = NetworkNameModel.Default(DEFAULT_NAME)
+ private const val SEP = "-"
+ private const val BUFFER_SEPARATOR = "|"
+ }
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionRepositoryKairosTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionRepositoryKairosTest.kt
new file mode 100644
index 000000000000..32fc35934bd6
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionRepositoryKairosTest.kt
@@ -0,0 +1,1228 @@
+/*
+ * 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.data.repository.prod
+
+import android.content.BroadcastReceiver
+import android.content.Context
+import android.content.Intent
+import android.net.ConnectivityManager.NetworkCallback
+import android.net.connectivityManager
+import android.platform.test.annotations.DisableFlags
+import android.platform.test.annotations.EnableFlags
+import android.telephony.AccessNetworkConstants.TRANSPORT_TYPE_WLAN
+import android.telephony.AccessNetworkConstants.TRANSPORT_TYPE_WWAN
+import android.telephony.CarrierConfigManager.KEY_INFLATE_SIGNAL_STRENGTH_BOOL
+import android.telephony.CarrierConfigManager.KEY_SHOW_5G_SLICE_ICON_BOOL
+import android.telephony.NetworkRegistrationInfo
+import android.telephony.NetworkRegistrationInfo.DOMAIN_PS
+import android.telephony.NetworkRegistrationInfo.REGISTRATION_STATE_DENIED
+import android.telephony.NetworkRegistrationInfo.REGISTRATION_STATE_HOME
+import android.telephony.ServiceState
+import android.telephony.ServiceState.STATE_IN_SERVICE
+import android.telephony.ServiceState.STATE_OUT_OF_SERVICE
+import android.telephony.SubscriptionManager.EXTRA_SUBSCRIPTION_INDEX
+import android.telephony.SubscriptionManager.PROFILE_CLASS_UNSET
+import android.telephony.TelephonyCallback
+import android.telephony.TelephonyCallback.CarrierRoamingNtnListener
+import android.telephony.TelephonyCallback.DataActivityListener
+import android.telephony.TelephonyCallback.DisplayInfoListener
+import android.telephony.TelephonyCallback.ServiceStateListener
+import android.telephony.TelephonyDisplayInfo
+import android.telephony.TelephonyDisplayInfo.OVERRIDE_NETWORK_TYPE_LTE_CA
+import android.telephony.TelephonyDisplayInfo.OVERRIDE_NETWORK_TYPE_NONE
+import android.telephony.TelephonyManager
+import android.telephony.TelephonyManager.DATA_ACTIVITY_DORMANT
+import android.telephony.TelephonyManager.DATA_ACTIVITY_IN
+import android.telephony.TelephonyManager.DATA_ACTIVITY_INOUT
+import android.telephony.TelephonyManager.DATA_ACTIVITY_NONE
+import android.telephony.TelephonyManager.DATA_ACTIVITY_OUT
+import android.telephony.TelephonyManager.DATA_CONNECTED
+import android.telephony.TelephonyManager.DATA_CONNECTING
+import android.telephony.TelephonyManager.DATA_DISCONNECTED
+import android.telephony.TelephonyManager.DATA_DISCONNECTING
+import android.telephony.TelephonyManager.DATA_HANDOVER_IN_PROGRESS
+import android.telephony.TelephonyManager.DATA_SUSPENDED
+import android.telephony.TelephonyManager.DATA_UNKNOWN
+import android.telephony.TelephonyManager.ERI_OFF
+import android.telephony.TelephonyManager.ERI_ON
+import android.telephony.TelephonyManager.EXTRA_CARRIER_ID
+import android.telephony.TelephonyManager.EXTRA_DATA_SPN
+import android.telephony.TelephonyManager.EXTRA_PLMN
+import android.telephony.TelephonyManager.EXTRA_SHOW_PLMN
+import android.telephony.TelephonyManager.EXTRA_SHOW_SPN
+import android.telephony.TelephonyManager.EXTRA_SPN
+import android.telephony.TelephonyManager.EXTRA_SUBSCRIPTION_ID
+import android.telephony.TelephonyManager.NETWORK_TYPE_LTE
+import android.telephony.TelephonyManager.NETWORK_TYPE_UNKNOWN
+import android.telephony.telephonyManager
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.settingslib.mobile.MobileMappings
+import com.android.systemui.Flags
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.flags.Flags.ROAMING_INDICATOR_VIA_DISPLAY_INFO
+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.runKairosTest
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.Kosmos.Fixture
+import com.android.systemui.kosmos.testDispatcher
+import com.android.systemui.kosmos.useUnconfinedTestDispatcher
+import com.android.systemui.log.LogBuffer
+import com.android.systemui.log.LogcatEchoTrackerAlways
+import com.android.systemui.log.table.TableLogBuffer
+import com.android.systemui.log.table.tableLogBufferFactory
+import com.android.systemui.statusbar.pipeline.mobile.data.MobileInputLogger
+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.DefaultNetworkType
+import com.android.systemui.statusbar.pipeline.mobile.data.model.ResolvedNetworkType.OverrideNetworkType
+import com.android.systemui.statusbar.pipeline.mobile.data.model.ResolvedNetworkType.UnknownNetworkType
+import com.android.systemui.statusbar.pipeline.mobile.data.model.SubscriptionModel
+import com.android.systemui.statusbar.pipeline.mobile.data.model.SystemUiCarrierConfig
+import com.android.systemui.statusbar.pipeline.mobile.data.model.testCarrierConfig
+import com.android.systemui.statusbar.pipeline.mobile.data.model.testCarrierConfigWithOverride
+import com.android.systemui.statusbar.pipeline.mobile.data.repository.MobileConnectionRepository.Companion.DEFAULT_NUM_LEVELS
+import com.android.systemui.statusbar.pipeline.mobile.data.repository.mobileMappingsProxy
+import com.android.systemui.statusbar.pipeline.mobile.data.repository.prod.MobileTelephonyHelpers.signalStrength
+import com.android.systemui.statusbar.pipeline.mobile.data.repository.prod.MobileTelephonyHelpers.telephonyDisplayInfo
+import com.android.systemui.statusbar.pipeline.shared.data.model.DataActivityModel
+import com.android.systemui.statusbar.pipeline.shared.data.model.toMobileDataActivityModel
+import com.android.systemui.testKosmos
+import com.google.common.truth.Truth.assertThat
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Mockito.verify
+import org.mockito.kotlin.any
+import org.mockito.kotlin.argumentCaptor
+import org.mockito.kotlin.doReturn
+import org.mockito.kotlin.mock
+import org.mockito.kotlin.stub
+import org.mockito.kotlin.whenever
+
+@OptIn(ExperimentalKairosApi::class)
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class MobileConnectionRepositoryKairosTest : SysuiTestCase() {
+
+ private val Kosmos.underTest by ActivatedKairosFixture {
+ MobileConnectionRepositoryKairosImpl(
+ SUB_1_ID,
+ context,
+ subscriptionModel,
+ DEFAULT_NAME_MODEL,
+ SEP,
+ connectivityManager,
+ telephonyManager,
+ systemUiCarrierConfig,
+ fakeBroadcastDispatcher,
+ mobileMappingsProxy,
+ testDispatcher,
+ logger,
+ tableLogger,
+ featureFlagsClassic,
+ )
+ }
+
+ private val Kosmos.logger: MobileInputLogger by Fixture {
+ MobileInputLogger(LogBuffer("test_buffer", 1, LogcatEchoTrackerAlways()))
+ }
+
+ private val Kosmos.tableLogger: TableLogBuffer by Fixture {
+ tableLogBufferFactory.getOrCreate("test_buffer", 1)
+ }
+
+ private val Kosmos.context: Context by Fixture { mock() }
+
+ private val systemUiCarrierConfig = SystemUiCarrierConfig(SUB_1_ID, testCarrierConfig())
+
+ private val Kosmos.subscriptionModel: MutableState<SubscriptionModel?> by Fixture {
+ MutableState(
+ kairos,
+ SubscriptionModel(
+ subscriptionId = SUB_1_ID,
+ carrierName = DEFAULT_NAME,
+ profileClass = PROFILE_CLASS_UNSET,
+ ),
+ )
+ }
+
+ private val kosmos =
+ testKosmos().apply {
+ useUnconfinedTestDispatcher()
+ featureFlagsClassic.fake.set(ROAMING_INDICATOR_VIA_DISPLAY_INFO, true)
+ telephonyManager.stub { on { subscriptionId } doReturn SUB_1_ID }
+ }
+
+ private fun runTest(block: suspend KairosTestScope.() -> Unit) =
+ kosmos.run { runKairosTest { block() } }
+
+ @Test
+ fun emergencyOnly() = runTest {
+ val latest by underTest.isEmergencyOnly.collectLastValue()
+
+ val serviceState = ServiceState().apply { isEmergencyOnly = true }
+
+ getTelephonyCallbackForType<ServiceStateListener>().onServiceStateChanged(serviceState)
+
+ assertThat(latest).isEqualTo(true)
+ }
+
+ @Test
+ fun emergencyOnly_toggles() = runTest {
+ val latest by underTest.isEmergencyOnly.collectLastValue()
+
+ val callback = getTelephonyCallbackForType<ServiceStateListener>()
+ callback.onServiceStateChanged(ServiceState().apply { isEmergencyOnly = true })
+
+ assertThat(latest).isTrue()
+
+ callback.onServiceStateChanged(ServiceState().apply { isEmergencyOnly = false })
+
+ assertThat(latest).isFalse()
+ }
+
+ @Test
+ fun cdmaLevelUpdates() = runTest {
+ val latest by underTest.cdmaLevel.collectLastValue()
+
+ val callback = getTelephonyCallbackForType<TelephonyCallback.SignalStrengthsListener>()
+ var strength = signalStrength(gsmLevel = 1, cdmaLevel = 2, isGsm = true)
+ callback.onSignalStrengthsChanged(strength)
+
+ assertThat(latest).isEqualTo(2)
+
+ // gsmLevel updates, no change to cdmaLevel
+ strength = signalStrength(gsmLevel = 3, cdmaLevel = 2, isGsm = true)
+ callback.onSignalStrengthsChanged(strength)
+
+ assertThat(latest).isEqualTo(2)
+ }
+
+ @Test
+ fun gsmLevelUpdates() = runTest {
+ val latest by underTest.primaryLevel.collectLastValue()
+
+ val callback = getTelephonyCallbackForType<TelephonyCallback.SignalStrengthsListener>()
+ var strength = signalStrength(gsmLevel = 1, cdmaLevel = 2, isGsm = true)
+ callback.onSignalStrengthsChanged(strength)
+
+ assertThat(latest).isEqualTo(1)
+
+ strength = signalStrength(gsmLevel = 3, cdmaLevel = 2, isGsm = true)
+ callback.onSignalStrengthsChanged(strength)
+
+ assertThat(latest).isEqualTo(3)
+ }
+
+ @Test
+ fun isGsm() = runTest {
+ val latest by underTest.isGsm.collectLastValue()
+
+ val callback = getTelephonyCallbackForType<TelephonyCallback.SignalStrengthsListener>()
+ var strength = signalStrength(gsmLevel = 1, cdmaLevel = 2, isGsm = true)
+ callback.onSignalStrengthsChanged(strength)
+
+ assertThat(latest).isTrue()
+
+ strength = signalStrength(gsmLevel = 1, cdmaLevel = 2, isGsm = false)
+ callback.onSignalStrengthsChanged(strength)
+
+ assertThat(latest).isFalse()
+ }
+
+ @Test
+ fun dataConnectionState_connected() = runTest {
+ val latest by underTest.dataConnectionState.collectLastValue()
+
+ val callback = getTelephonyCallbackForType<TelephonyCallback.DataConnectionStateListener>()
+ callback.onDataConnectionStateChanged(DATA_CONNECTED, 200 /* unused */)
+
+ assertThat(latest).isEqualTo(DataConnectionState.Connected)
+ }
+
+ @Test
+ fun dataConnectionState_connecting() = runTest {
+ val latest by underTest.dataConnectionState.collectLastValue()
+
+ val callback = getTelephonyCallbackForType<TelephonyCallback.DataConnectionStateListener>()
+ callback.onDataConnectionStateChanged(DATA_CONNECTING, 200 /* unused */)
+
+ assertThat(latest).isEqualTo(DataConnectionState.Connecting)
+ }
+
+ @Test
+ fun dataConnectionState_disconnected() = runTest {
+ val latest by underTest.dataConnectionState.collectLastValue()
+
+ val callback = getTelephonyCallbackForType<TelephonyCallback.DataConnectionStateListener>()
+ callback.onDataConnectionStateChanged(DATA_DISCONNECTED, 200 /* unused */)
+
+ assertThat(latest).isEqualTo(DataConnectionState.Disconnected)
+ }
+
+ @Test
+ fun dataConnectionState_disconnecting() = runTest {
+ val latest by underTest.dataConnectionState.collectLastValue()
+
+ val callback = getTelephonyCallbackForType<TelephonyCallback.DataConnectionStateListener>()
+ callback.onDataConnectionStateChanged(DATA_DISCONNECTING, 200 /* unused */)
+
+ assertThat(latest).isEqualTo(DataConnectionState.Disconnecting)
+ }
+
+ @Test
+ fun dataConnectionState_suspended() = runTest {
+ val latest by underTest.dataConnectionState.collectLastValue()
+
+ val callback = getTelephonyCallbackForType<TelephonyCallback.DataConnectionStateListener>()
+ callback.onDataConnectionStateChanged(DATA_SUSPENDED, 200 /* unused */)
+
+ assertThat(latest).isEqualTo(DataConnectionState.Suspended)
+ }
+
+ @Test
+ fun dataConnectionState_handoverInProgress() = runTest {
+ val latest by underTest.dataConnectionState.collectLastValue()
+
+ val callback = getTelephonyCallbackForType<TelephonyCallback.DataConnectionStateListener>()
+ callback.onDataConnectionStateChanged(DATA_HANDOVER_IN_PROGRESS, 200 /* unused */)
+
+ assertThat(latest).isEqualTo(DataConnectionState.HandoverInProgress)
+ }
+
+ @Test
+ fun dataConnectionState_unknown() = runTest {
+ val latest by underTest.dataConnectionState.collectLastValue()
+
+ val callback = getTelephonyCallbackForType<TelephonyCallback.DataConnectionStateListener>()
+ callback.onDataConnectionStateChanged(DATA_UNKNOWN, 200 /* unused */)
+
+ assertThat(latest).isEqualTo(DataConnectionState.Unknown)
+ }
+
+ @Test
+ fun dataConnectionState_invalid() = runTest {
+ val latest by underTest.dataConnectionState.collectLastValue()
+
+ val callback = getTelephonyCallbackForType<TelephonyCallback.DataConnectionStateListener>()
+ callback.onDataConnectionStateChanged(45, 200 /* unused */)
+
+ assertThat(latest).isEqualTo(DataConnectionState.Invalid)
+ }
+
+ @Test
+ fun dataActivity() = runTest {
+ val latest by underTest.dataActivityDirection.collectLastValue()
+
+ val callback = getTelephonyCallbackForType<DataActivityListener>()
+ callback.onDataActivity(DATA_ACTIVITY_INOUT)
+
+ assertThat(latest).isEqualTo(DATA_ACTIVITY_INOUT.toMobileDataActivityModel())
+ }
+
+ @Test
+ fun carrierId_initialValueCaptured() = runTest {
+ whenever(telephonyManager.simCarrierId).thenReturn(1234)
+
+ val latest by underTest.carrierId.collectLastValue()
+
+ assertThat(latest).isEqualTo(1234)
+ }
+
+ @Test
+ fun carrierId_updatesOnBroadcast() = runTest {
+ whenever(telephonyManager.simCarrierId).thenReturn(1234)
+
+ val latest by underTest.carrierId.collectLastValue()
+
+ fakeBroadcastDispatcher.sendIntentToMatchingReceiversOnly(
+ context,
+ carrierIdIntent(carrierId = 4321),
+ )
+
+ assertThat(latest).isEqualTo(4321)
+ }
+
+ @Test
+ fun carrierNetworkChange() = runTest {
+ val latest by underTest.carrierNetworkChangeActive.collectLastValue()
+
+ val callback = getTelephonyCallbackForType<TelephonyCallback.CarrierNetworkListener>()
+ callback.onCarrierNetworkChange(true)
+
+ assertThat(latest).isEqualTo(true)
+ }
+
+ @Test
+ fun networkType_default() = runTest {
+ val latest by underTest.resolvedNetworkType.collectLastValue()
+
+ val expected = UnknownNetworkType
+
+ assertThat(latest).isEqualTo(expected)
+ }
+
+ @Test
+ fun networkType_unknown_hasCorrectKey() = runTest {
+ val latest by underTest.resolvedNetworkType.collectLastValue()
+
+ val callback = getTelephonyCallbackForType<TelephonyCallback.DisplayInfoListener>()
+ val ti =
+ telephonyDisplayInfo(
+ networkType = NETWORK_TYPE_UNKNOWN,
+ overrideNetworkType = NETWORK_TYPE_UNKNOWN,
+ )
+
+ callback.onDisplayInfoChanged(ti)
+
+ val expected = UnknownNetworkType
+ assertThat(latest).isEqualTo(expected)
+ assertThat(latest!!.lookupKey).isEqualTo(MobileMappings.toIconKey(NETWORK_TYPE_UNKNOWN))
+ }
+
+ @Test
+ fun networkType_updatesUsingDefault() = runTest {
+ val latest by underTest.resolvedNetworkType.collectLastValue()
+
+ val callback = getTelephonyCallbackForType<TelephonyCallback.DisplayInfoListener>()
+ val overrideType = OVERRIDE_NETWORK_TYPE_NONE
+ val type = NETWORK_TYPE_LTE
+ val ti = telephonyDisplayInfo(networkType = type, overrideNetworkType = overrideType)
+ callback.onDisplayInfoChanged(ti)
+
+ val expected = DefaultNetworkType(mobileMappingsProxy.toIconKey(type))
+ assertThat(latest).isEqualTo(expected)
+ }
+
+ @Test
+ fun networkType_updatesUsingOverride() = runTest {
+ val latest by underTest.resolvedNetworkType.collectLastValue()
+
+ val callback = getTelephonyCallbackForType<TelephonyCallback.DisplayInfoListener>()
+ val type = OVERRIDE_NETWORK_TYPE_LTE_CA
+ val ti = telephonyDisplayInfo(networkType = type, overrideNetworkType = type)
+ callback.onDisplayInfoChanged(ti)
+
+ val expected = OverrideNetworkType(mobileMappingsProxy.toIconKeyOverride(type))
+ assertThat(latest).isEqualTo(expected)
+ }
+
+ @Test
+ fun networkType_unknownNetworkWithOverride_usesOverrideKey() = runTest {
+ val latest by underTest.resolvedNetworkType.collectLastValue()
+
+ val callback = getTelephonyCallbackForType<TelephonyCallback.DisplayInfoListener>()
+ val unknown = NETWORK_TYPE_UNKNOWN
+ val type = OVERRIDE_NETWORK_TYPE_LTE_CA
+ val ti = telephonyDisplayInfo(unknown, type)
+ callback.onDisplayInfoChanged(ti)
+
+ val expected = OverrideNetworkType(mobileMappingsProxy.toIconKeyOverride(type))
+ assertThat(latest).isEqualTo(expected)
+ }
+
+ @Test
+ fun dataEnabled_initial_false() = runTest {
+ whenever(telephonyManager.isDataConnectionAllowed).thenReturn(false)
+
+ val latest by underTest.dataEnabled.collectLastValue()
+
+ assertThat(latest).isFalse()
+ }
+
+ @Test
+ fun isDataEnabled_tracksTelephonyCallback() = runTest {
+ val latest by underTest.dataEnabled.collectLastValue()
+
+ whenever(telephonyManager.isDataConnectionAllowed).thenReturn(false)
+ assertThat(latest).isFalse()
+
+ val callback = getTelephonyCallbackForType<TelephonyCallback.DataEnabledListener>()
+
+ callback.onDataEnabledChanged(true, 1)
+ assertThat(latest).isTrue()
+
+ callback.onDataEnabledChanged(false, 1)
+ assertThat(latest).isFalse()
+ }
+
+ @Test
+ fun numberOfLevels_isDefault() = runTest {
+ val latest by underTest.numberOfLevels.collectLastValue()
+
+ assertThat(latest).isEqualTo(DEFAULT_NUM_LEVELS)
+ }
+
+ @Test
+ fun roaming_cdma_queriesTelephonyManager() = runTest {
+ val latest by underTest.cdmaRoaming.collectLastValue()
+
+ val cb = getTelephonyCallbackForType<ServiceStateListener>()
+
+ // CDMA roaming is off, GSM roaming is on
+ whenever(telephonyManager.cdmaEnhancedRoamingIndicatorDisplayNumber).thenReturn(ERI_OFF)
+ cb.onServiceStateChanged(ServiceState().also { it.roaming = true })
+
+ assertThat(latest).isFalse()
+
+ // CDMA roaming is on, GSM roaming is off
+ whenever(telephonyManager.cdmaEnhancedRoamingIndicatorDisplayNumber).thenReturn(ERI_ON)
+ cb.onServiceStateChanged(ServiceState().also { it.roaming = false })
+
+ assertThat(latest).isTrue()
+ }
+
+ /**
+ * [TelephonyManager.getCdmaEnhancedRoamingIndicatorDisplayNumber] returns -1 if the service is
+ * not running or if there is an error while retrieving the cdma ERI
+ */
+ @Test
+ fun cdmaRoaming_ignoresNegativeOne() = runTest {
+ val latest by underTest.cdmaRoaming.collectLastValue()
+
+ val serviceState = ServiceState()
+ serviceState.roaming = false
+
+ val cb = getTelephonyCallbackForType<ServiceStateListener>()
+
+ // CDMA roaming is unavailable (-1), GSM roaming is off
+ whenever(telephonyManager.cdmaEnhancedRoamingIndicatorDisplayNumber).thenReturn(-1)
+ cb.onServiceStateChanged(serviceState)
+
+ assertThat(latest).isFalse()
+ }
+
+ @Test
+ fun roaming_gsm_queriesDisplayInfo_viaDisplayInfo() = runTest {
+ // GIVEN flag is true
+ featureFlagsClassic.fake.set(ROAMING_INDICATOR_VIA_DISPLAY_INFO, true)
+
+ val latest by underTest.isRoaming.collectLastValue()
+
+ val cb = getTelephonyCallbackForType<DisplayInfoListener>()
+
+ // CDMA roaming is off, GSM roaming is off
+ whenever(telephonyManager.cdmaEnhancedRoamingIndicatorDisplayNumber).thenReturn(ERI_OFF)
+ cb.onDisplayInfoChanged(TelephonyDisplayInfo(NETWORK_TYPE_LTE, NETWORK_TYPE_UNKNOWN, false))
+
+ assertThat(latest).isFalse()
+
+ // CDMA roaming is off, GSM roaming is on
+ cb.onDisplayInfoChanged(TelephonyDisplayInfo(NETWORK_TYPE_LTE, NETWORK_TYPE_UNKNOWN, true))
+
+ assertThat(latest).isTrue()
+ }
+
+ @Test
+ fun roaming_gsm_queriesDisplayInfo_viaServiceState() = runTest {
+ // GIVEN flag is false
+ featureFlagsClassic.fake.set(ROAMING_INDICATOR_VIA_DISPLAY_INFO, false)
+
+ val latest by underTest.isRoaming.collectLastValue()
+
+ val cb = getTelephonyCallbackForType<ServiceStateListener>()
+
+ // CDMA roaming is off, GSM roaming is off
+ whenever(telephonyManager.cdmaEnhancedRoamingIndicatorDisplayNumber).thenReturn(ERI_OFF)
+ cb.onServiceStateChanged(ServiceState().also { it.roaming = false })
+
+ assertThat(latest).isFalse()
+
+ // CDMA roaming is off, GSM roaming is on
+ cb.onServiceStateChanged(ServiceState().also { it.roaming = true })
+
+ assertThat(latest).isTrue()
+ }
+
+ @Test
+ fun activity_updatesFromCallback() = runTest {
+ val latest by underTest.dataActivityDirection.collectLastValue()
+
+ assertThat(latest)
+ .isEqualTo(DataActivityModel(hasActivityIn = false, hasActivityOut = false))
+
+ val cb = getTelephonyCallbackForType<DataActivityListener>()
+ cb.onDataActivity(DATA_ACTIVITY_IN)
+ assertThat(latest)
+ .isEqualTo(DataActivityModel(hasActivityIn = true, hasActivityOut = false))
+
+ cb.onDataActivity(DATA_ACTIVITY_OUT)
+ assertThat(latest)
+ .isEqualTo(DataActivityModel(hasActivityIn = false, hasActivityOut = true))
+
+ cb.onDataActivity(DATA_ACTIVITY_INOUT)
+ assertThat(latest).isEqualTo(DataActivityModel(hasActivityIn = true, hasActivityOut = true))
+
+ cb.onDataActivity(DATA_ACTIVITY_NONE)
+ assertThat(latest)
+ .isEqualTo(DataActivityModel(hasActivityIn = false, hasActivityOut = false))
+
+ cb.onDataActivity(DATA_ACTIVITY_DORMANT)
+ assertThat(latest)
+ .isEqualTo(DataActivityModel(hasActivityIn = false, hasActivityOut = false))
+
+ cb.onDataActivity(1234)
+ assertThat(latest)
+ .isEqualTo(DataActivityModel(hasActivityIn = false, hasActivityOut = false))
+ }
+
+ @Test
+ fun networkNameForSubId_updates() = runTest {
+ val latest by underTest.carrierName.collectLastValue()
+
+ subscriptionModel.setValue(
+ SubscriptionModel(
+ subscriptionId = SUB_1_ID,
+ carrierName = DEFAULT_NAME,
+ profileClass = PROFILE_CLASS_UNSET,
+ )
+ )
+
+ assertThat(latest?.name).isEqualTo(DEFAULT_NAME)
+
+ val updatedName = "Derived Carrier"
+ subscriptionModel.setValue(
+ SubscriptionModel(
+ subscriptionId = SUB_1_ID,
+ carrierName = updatedName,
+ profileClass = PROFILE_CLASS_UNSET,
+ )
+ )
+
+ assertThat(latest?.name).isEqualTo(updatedName)
+ }
+
+ @Test
+ fun networkNameForSubId_defaultWhenSubscriptionModelNull() = runTest {
+ val latest by underTest.carrierName.collectLastValue()
+
+ subscriptionModel.setValue(null)
+
+ assertThat(latest?.name).isEqualTo(DEFAULT_NAME)
+ }
+
+ @Test
+ fun networkName_default() = runTest {
+ val latest by underTest.networkName.collectLastValue()
+
+ assertThat(latest).isEqualTo(DEFAULT_NAME_MODEL)
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_STATUS_BAR_SWITCH_TO_SPN_FROM_DATA_SPN)
+ fun networkName_usesBroadcastInfo_returnsDerived() = runTest {
+ val latest by underTest.networkName.collectLastValue()
+
+ val intent = spnIntent()
+ val captor = argumentCaptor<BroadcastReceiver>()
+ verify(context).registerReceiver(captor.capture(), any())
+ captor.lastValue.onReceive(context, intent)
+
+ // spnIntent() sets all values to true and test strings
+ assertThat(latest).isEqualTo(NetworkNameModel.IntentDerived("$PLMN$SEP$DATA_SPN"))
+ }
+
+ @Test
+ @DisableFlags(Flags.FLAG_STATUS_BAR_SWITCH_TO_SPN_FROM_DATA_SPN)
+ fun networkName_usesBroadcastInfo_returnsDerived_flagOff() = runTest {
+ val latest by underTest.networkName.collectLastValue()
+
+ val intent = spnIntent()
+ val captor = argumentCaptor<BroadcastReceiver>()
+ verify(context).registerReceiver(captor.capture(), any())
+ captor.lastValue.onReceive(context, intent)
+
+ // spnIntent() sets all values to true and test strings
+ assertThat(latest).isEqualTo(NetworkNameModel.IntentDerived("$PLMN$SEP$DATA_SPN"))
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_STATUS_BAR_SWITCH_TO_SPN_FROM_DATA_SPN)
+ fun networkName_broadcastNotForThisSubId_keepsOldValue() = runTest {
+ val latest by underTest.networkName.collectLastValue()
+
+ val intent = spnIntent()
+ val captor = argumentCaptor<BroadcastReceiver>()
+ verify(context).registerReceiver(captor.capture(), any())
+ captor.lastValue.onReceive(context, intent)
+
+ assertThat(latest).isEqualTo(NetworkNameModel.IntentDerived("$PLMN$SEP$DATA_SPN"))
+
+ // WHEN an intent with a different subId is sent
+ val wrongSubIntent = spnIntent(subId = 101)
+
+ captor.lastValue.onReceive(context, wrongSubIntent)
+
+ // THEN the previous intent's name is still used
+ assertThat(latest).isEqualTo(NetworkNameModel.IntentDerived("$PLMN$SEP$DATA_SPN"))
+ }
+
+ @Test
+ @DisableFlags(Flags.FLAG_STATUS_BAR_SWITCH_TO_SPN_FROM_DATA_SPN)
+ fun networkName_broadcastNotForThisSubId_keepsOldValue_flagOff() = runTest {
+ val latest by underTest.networkName.collectLastValue()
+
+ val intent = spnIntent()
+ val captor = argumentCaptor<BroadcastReceiver>()
+ verify(context).registerReceiver(captor.capture(), any())
+ captor.lastValue.onReceive(context, intent)
+
+ assertThat(latest).isEqualTo(NetworkNameModel.IntentDerived("$PLMN$SEP$DATA_SPN"))
+
+ // WHEN an intent with a different subId is sent
+ val wrongSubIntent = spnIntent(subId = 101)
+
+ captor.lastValue.onReceive(context, wrongSubIntent)
+
+ // THEN the previous intent's name is still used
+ assertThat(latest).isEqualTo(NetworkNameModel.IntentDerived("$PLMN$SEP$DATA_SPN"))
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_STATUS_BAR_SWITCH_TO_SPN_FROM_DATA_SPN)
+ fun networkName_broadcastHasNoData_updatesToDefault() = runTest {
+ val latest by underTest.networkName.collectLastValue()
+
+ val intent = spnIntent()
+ val captor = argumentCaptor<BroadcastReceiver>()
+ verify(context).registerReceiver(captor.capture(), any())
+ captor.lastValue.onReceive(context, intent)
+
+ assertThat(latest).isEqualTo(NetworkNameModel.IntentDerived("$PLMN$SEP$DATA_SPN"))
+
+ val intentWithoutInfo = spnIntent(showSpn = false, showPlmn = false)
+
+ captor.lastValue.onReceive(context, intentWithoutInfo)
+
+ assertThat(latest).isEqualTo(DEFAULT_NAME_MODEL)
+ }
+
+ @Test
+ @DisableFlags(Flags.FLAG_STATUS_BAR_SWITCH_TO_SPN_FROM_DATA_SPN)
+ fun networkName_broadcastHasNoData_updatesToDefault_flagOff() = runTest {
+ val latest by underTest.networkName.collectLastValue()
+
+ val intent = spnIntent()
+ val captor = argumentCaptor<BroadcastReceiver>()
+ verify(context).registerReceiver(captor.capture(), any())
+ captor.lastValue.onReceive(context, intent)
+
+ assertThat(latest).isEqualTo(NetworkNameModel.IntentDerived("$PLMN$SEP$DATA_SPN"))
+
+ val intentWithoutInfo = spnIntent(showSpn = false, showPlmn = false)
+
+ captor.lastValue.onReceive(context, intentWithoutInfo)
+
+ assertThat(latest).isEqualTo(DEFAULT_NAME_MODEL)
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_STATUS_BAR_SWITCH_TO_SPN_FROM_DATA_SPN)
+ fun networkName_usingEagerStrategy_retainsNameBetweenSubscribers() = runTest {
+ // Use the [StateFlow.value] getter so we can prove that the collection happens
+ // even when there is no [Job]
+
+ // Starts out default
+ val latest by underTest.networkName.collectLastValue()
+ assertThat(latest).isEqualTo(DEFAULT_NAME_MODEL)
+
+ val intent = spnIntent()
+ val captor = argumentCaptor<BroadcastReceiver>()
+ verify(context).registerReceiver(captor.capture(), any())
+ captor.lastValue.onReceive(context, intent)
+
+ // The value is still there despite no active subscribers
+ assertThat(latest).isEqualTo(NetworkNameModel.IntentDerived("$PLMN$SEP$DATA_SPN"))
+ }
+
+ @Test
+ @DisableFlags(Flags.FLAG_STATUS_BAR_SWITCH_TO_SPN_FROM_DATA_SPN)
+ fun networkName_usingEagerStrategy_retainsNameBetweenSubscribers_flagOff() = runTest {
+ // Use the [StateFlow.value] getter so we can prove that the collection happens
+ // even when there is no [Job]
+
+ // Starts out default
+ val latest by underTest.networkName.collectLastValue()
+ assertThat(latest).isEqualTo(DEFAULT_NAME_MODEL)
+
+ val intent = spnIntent()
+ val captor = argumentCaptor<BroadcastReceiver>()
+ verify(context).registerReceiver(captor.capture(), any())
+ captor.lastValue.onReceive(context, intent)
+
+ // The value is still there despite no active subscribers
+ assertThat(latest).isEqualTo(NetworkNameModel.IntentDerived("$PLMN$SEP$DATA_SPN"))
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_STATUS_BAR_SWITCH_TO_SPN_FROM_DATA_SPN)
+ fun networkName_allFieldsSet_prioritizesDataSpnOverSpn() = runTest {
+ val latest by underTest.networkName.collectLastValue()
+ val captor = argumentCaptor<BroadcastReceiver>()
+ verify(context).registerReceiver(captor.capture(), any())
+
+ val intent =
+ spnIntent(
+ subId = SUB_1_ID,
+ showSpn = true,
+ spn = SPN,
+ dataSpn = DATA_SPN,
+ showPlmn = true,
+ plmn = PLMN,
+ )
+ captor.lastValue.onReceive(context, intent)
+ assertThat(latest).isEqualTo(NetworkNameModel.IntentDerived("$PLMN$SEP$DATA_SPN"))
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_STATUS_BAR_SWITCH_TO_SPN_FROM_DATA_SPN)
+ fun networkName_spnAndPlmn_fallbackToSpnWhenNullDataSpn() = runTest {
+ val latest by underTest.networkName.collectLastValue()
+ val captor = argumentCaptor<BroadcastReceiver>()
+ verify(context).registerReceiver(captor.capture(), any())
+
+ val intent =
+ spnIntent(
+ subId = SUB_1_ID,
+ showSpn = true,
+ spn = SPN,
+ dataSpn = null,
+ showPlmn = true,
+ plmn = PLMN,
+ )
+ captor.lastValue.onReceive(context, intent)
+ assertThat(latest).isEqualTo(NetworkNameModel.IntentDerived("$PLMN$SEP$SPN"))
+ }
+
+ @Test
+ @DisableFlags(Flags.FLAG_STATUS_BAR_SWITCH_TO_SPN_FROM_DATA_SPN)
+ fun networkName_allFieldsSet_flagOff() = runTest {
+ val latest by underTest.networkName.collectLastValue()
+ val captor = argumentCaptor<BroadcastReceiver>()
+ verify(context).registerReceiver(captor.capture(), any())
+
+ val intent =
+ spnIntent(
+ subId = SUB_1_ID,
+ showSpn = true,
+ spn = SPN,
+ dataSpn = DATA_SPN,
+ showPlmn = true,
+ plmn = PLMN,
+ )
+ captor.lastValue.onReceive(context, intent)
+ assertThat(latest).isEqualTo(NetworkNameModel.IntentDerived("$PLMN$SEP$DATA_SPN"))
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_STATUS_BAR_SWITCH_TO_SPN_FROM_DATA_SPN)
+ fun networkName_showPlmn_plmnNotNull_showSpn_spnNull_dataSpnNotNull() = runTest {
+ val latest by underTest.networkName.collectLastValue()
+ val captor = argumentCaptor<BroadcastReceiver>()
+ verify(context).registerReceiver(captor.capture(), any())
+ val intent =
+ spnIntent(
+ subId = SUB_1_ID,
+ showSpn = true,
+ spn = null,
+ dataSpn = DATA_SPN,
+ showPlmn = true,
+ plmn = PLMN,
+ )
+ captor.lastValue.onReceive(context, intent)
+ assertThat(latest).isEqualTo(NetworkNameModel.IntentDerived("$PLMN$SEP$DATA_SPN"))
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_STATUS_BAR_SWITCH_TO_SPN_FROM_DATA_SPN)
+ fun networkName_showPlmn_plmnNotNull_showSpn_spnNotNull_dataSpnNull() = runTest {
+ val latest by underTest.networkName.collectLastValue()
+ val captor = argumentCaptor<BroadcastReceiver>()
+ verify(context).registerReceiver(captor.capture(), any())
+ val intent =
+ spnIntent(
+ subId = SUB_1_ID,
+ showSpn = true,
+ spn = SPN,
+ dataSpn = null,
+ showPlmn = true,
+ plmn = PLMN,
+ )
+ captor.lastValue.onReceive(context, intent)
+ assertThat(latest).isEqualTo(NetworkNameModel.IntentDerived("$PLMN$SEP$SPN"))
+ }
+
+ @Test
+ @DisableFlags(Flags.FLAG_STATUS_BAR_SWITCH_TO_SPN_FROM_DATA_SPN)
+ fun networkName_showPlmn_plmnNotNull_showSpn_spnNull_dataSpnNotNull_flagOff() = runTest {
+ val latest by underTest.networkName.collectLastValue()
+ val captor = argumentCaptor<BroadcastReceiver>()
+ verify(context).registerReceiver(captor.capture(), any())
+ val intent =
+ spnIntent(
+ subId = SUB_1_ID,
+ showSpn = true,
+ spn = null,
+ dataSpn = DATA_SPN,
+ showPlmn = true,
+ plmn = PLMN,
+ )
+ captor.lastValue.onReceive(context, intent)
+ assertThat(latest).isEqualTo(NetworkNameModel.IntentDerived("$PLMN$SEP$DATA_SPN"))
+ }
+
+ @Test
+ fun networkName_showPlmn_noShowSPN() = runTest {
+ val latest by underTest.networkName.collectLastValue()
+ val captor = argumentCaptor<BroadcastReceiver>()
+ verify(context).registerReceiver(captor.capture(), any())
+ val intent =
+ spnIntent(
+ subId = SUB_1_ID,
+ showSpn = false,
+ spn = SPN,
+ dataSpn = DATA_SPN,
+ showPlmn = true,
+ plmn = PLMN,
+ )
+ captor.lastValue.onReceive(context, intent)
+ assertThat(latest).isEqualTo(NetworkNameModel.IntentDerived("$PLMN"))
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_STATUS_BAR_SWITCH_TO_SPN_FROM_DATA_SPN)
+ fun networkName_showPlmn_plmnNull_showSpn() = runTest {
+ val latest by underTest.networkName.collectLastValue()
+ val captor = argumentCaptor<BroadcastReceiver>()
+ verify(context).registerReceiver(captor.capture(), any())
+ val intent =
+ spnIntent(
+ subId = SUB_1_ID,
+ showSpn = true,
+ spn = SPN,
+ dataSpn = DATA_SPN,
+ showPlmn = true,
+ plmn = null,
+ )
+ captor.lastValue.onReceive(context, intent)
+ assertThat(latest).isEqualTo(NetworkNameModel.IntentDerived("$DATA_SPN"))
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_STATUS_BAR_SWITCH_TO_SPN_FROM_DATA_SPN)
+ fun networkName_showPlmn_plmnNull_showSpn_dataSpnNull() = runTest {
+ val latest by underTest.networkName.collectLastValue()
+ val captor = argumentCaptor<BroadcastReceiver>()
+ verify(context).registerReceiver(captor.capture(), any())
+ val intent =
+ spnIntent(
+ subId = SUB_1_ID,
+ showSpn = true,
+ spn = SPN,
+ dataSpn = null,
+ showPlmn = true,
+ plmn = null,
+ )
+ captor.lastValue.onReceive(context, intent)
+ assertThat(latest).isEqualTo(NetworkNameModel.IntentDerived("$SPN"))
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_STATUS_BAR_SWITCH_TO_SPN_FROM_DATA_SPN)
+ fun networkName_showPlmn_plmnNull_showSpn_bothSpnNull() = runTest {
+ val latest by underTest.networkName.collectLastValue()
+ val captor = argumentCaptor<BroadcastReceiver>()
+ verify(context).registerReceiver(captor.capture(), any())
+ val intent =
+ spnIntent(
+ subId = SUB_1_ID,
+ showSpn = true,
+ spn = null,
+ dataSpn = null,
+ showPlmn = true,
+ plmn = null,
+ )
+ captor.lastValue.onReceive(context, intent)
+ assertThat(latest).isEqualTo(DEFAULT_NAME_MODEL)
+ }
+
+ @Test
+ @DisableFlags(Flags.FLAG_STATUS_BAR_SWITCH_TO_SPN_FROM_DATA_SPN)
+ fun networkName_showPlmn_plmnNull_showSpn_flagOff() = runTest {
+ val latest by underTest.networkName.collectLastValue()
+ val captor = argumentCaptor<BroadcastReceiver>()
+ verify(context).registerReceiver(captor.capture(), any())
+ val intent =
+ spnIntent(
+ subId = SUB_1_ID,
+ showSpn = true,
+ spn = SPN,
+ dataSpn = DATA_SPN,
+ showPlmn = true,
+ plmn = null,
+ )
+ captor.lastValue.onReceive(context, intent)
+ assertThat(latest).isEqualTo(NetworkNameModel.IntentDerived("$DATA_SPN"))
+ }
+
+ @Test
+ fun operatorAlphaShort_tracked() = runTest {
+ val latest by underTest.operatorAlphaShort.collectLastValue()
+
+ val shortName = "short name"
+ val serviceState = ServiceState()
+ serviceState.setOperatorName(
+ /* longName */ "long name",
+ /* shortName */ shortName,
+ /* numeric */ "12345",
+ )
+
+ getTelephonyCallbackForType<ServiceStateListener>().onServiceStateChanged(serviceState)
+
+ assertThat(latest).isEqualTo(shortName)
+ }
+
+ @Test
+ fun isInService_notIwlan() = runTest {
+ val latest by underTest.isInService.collectLastValue()
+
+ val nriInService =
+ NetworkRegistrationInfo.Builder()
+ .setDomain(DOMAIN_PS)
+ .setTransportType(TRANSPORT_TYPE_WWAN)
+ .setRegistrationState(REGISTRATION_STATE_HOME)
+ .build()
+
+ getTelephonyCallbackForType<ServiceStateListener>()
+ .onServiceStateChanged(
+ ServiceState().also {
+ it.voiceRegState = STATE_IN_SERVICE
+ it.addNetworkRegistrationInfo(nriInService)
+ }
+ )
+
+ assertThat(latest).isTrue()
+
+ getTelephonyCallbackForType<ServiceStateListener>()
+ .onServiceStateChanged(
+ ServiceState().also {
+ it.voiceRegState = STATE_OUT_OF_SERVICE
+ it.addNetworkRegistrationInfo(nriInService)
+ }
+ )
+ assertThat(latest).isTrue()
+
+ val nriNotInService =
+ NetworkRegistrationInfo.Builder()
+ .setDomain(DOMAIN_PS)
+ .setTransportType(TRANSPORT_TYPE_WWAN)
+ .setRegistrationState(REGISTRATION_STATE_DENIED)
+ .build()
+ getTelephonyCallbackForType<ServiceStateListener>()
+ .onServiceStateChanged(
+ ServiceState().also {
+ it.voiceRegState = STATE_OUT_OF_SERVICE
+ it.addNetworkRegistrationInfo(nriNotInService)
+ }
+ )
+ assertThat(latest).isFalse()
+ }
+
+ @Test
+ fun isInService_isIwlan_voiceOutOfService_dataInService() = runTest {
+ val latest by underTest.isInService.collectLastValue()
+
+ val iwlanData =
+ NetworkRegistrationInfo.Builder()
+ .setDomain(DOMAIN_PS)
+ .setTransportType(TRANSPORT_TYPE_WLAN)
+ .setRegistrationState(REGISTRATION_STATE_HOME)
+ .build()
+ val serviceState =
+ ServiceState().also {
+ it.voiceRegState = STATE_OUT_OF_SERVICE
+ it.addNetworkRegistrationInfo(iwlanData)
+ }
+
+ getTelephonyCallbackForType<ServiceStateListener>().onServiceStateChanged(serviceState)
+ assertThat(latest).isFalse()
+ }
+
+ @Test
+ fun isNonTerrestrial_updatesFromCallback0() = runTest {
+ val latest by underTest.isNonTerrestrial.collectLastValue()
+
+ // Starts out false
+ assertThat(latest).isFalse()
+
+ val callback = getTelephonyCallbackForType<CarrierRoamingNtnListener>()
+
+ callback.onCarrierRoamingNtnModeChanged(true)
+ assertThat(latest).isTrue()
+
+ callback.onCarrierRoamingNtnModeChanged(false)
+ assertThat(latest).isFalse()
+ }
+
+ @Test
+ fun numberOfLevels_usesCarrierConfig() = runTest {
+ val latest by underTest.numberOfLevels.collectLastValue()
+
+ assertThat(latest).isEqualTo(DEFAULT_NUM_LEVELS)
+
+ systemUiCarrierConfig.processNewCarrierConfig(
+ testCarrierConfigWithOverride(KEY_INFLATE_SIGNAL_STRENGTH_BOOL, true)
+ )
+
+ assertThat(latest).isEqualTo(DEFAULT_NUM_LEVELS + 1)
+
+ systemUiCarrierConfig.processNewCarrierConfig(
+ testCarrierConfigWithOverride(KEY_INFLATE_SIGNAL_STRENGTH_BOOL, false)
+ )
+
+ assertThat(latest).isEqualTo(DEFAULT_NUM_LEVELS)
+ }
+
+ @Test
+ fun inflateSignalStrength_usesCarrierConfig() = runTest {
+ val latest by underTest.inflateSignalStrength.collectLastValue()
+
+ assertThat(latest).isEqualTo(false)
+
+ systemUiCarrierConfig.processNewCarrierConfig(
+ testCarrierConfigWithOverride(KEY_INFLATE_SIGNAL_STRENGTH_BOOL, true)
+ )
+
+ assertThat(latest).isEqualTo(true)
+
+ systemUiCarrierConfig.processNewCarrierConfig(
+ testCarrierConfigWithOverride(KEY_INFLATE_SIGNAL_STRENGTH_BOOL, false)
+ )
+
+ assertThat(latest).isEqualTo(false)
+ }
+
+ @Test
+ fun allowNetworkSliceIndicator_exposesCarrierConfigValue() = runTest {
+ val latest by underTest.allowNetworkSliceIndicator.collectLastValue()
+
+ systemUiCarrierConfig.processNewCarrierConfig(
+ testCarrierConfigWithOverride(KEY_SHOW_5G_SLICE_ICON_BOOL, true)
+ )
+
+ assertThat(latest).isTrue()
+
+ systemUiCarrierConfig.processNewCarrierConfig(
+ testCarrierConfigWithOverride(KEY_SHOW_5G_SLICE_ICON_BOOL, false)
+ )
+
+ assertThat(latest).isFalse()
+ }
+
+ @Test
+ fun isAllowedDuringAirplaneMode_alwaysFalse() = runTest {
+ val latest by underTest.isAllowedDuringAirplaneMode.collectLastValue()
+
+ assertThat(latest).isFalse()
+ }
+
+ @Test
+ fun hasPrioritizedCaps_defaultFalse() = runTest {
+ // stand up under-test to kick-off activation
+ underTest
+
+ assertThat(kairos.transact { underTest.hasPrioritizedNetworkCapabilities.sample() })
+ .isFalse()
+ }
+
+ @Test
+ fun hasPrioritizedCaps_trueWhenAvailable() = runTest {
+ val latest by underTest.hasPrioritizedNetworkCapabilities.collectLastValue()
+
+ val callback: NetworkCallback =
+ argumentCaptor<NetworkCallback>()
+ .apply { verify(connectivityManager).registerNetworkCallback(any(), capture()) }
+ .lastValue
+
+ callback.onAvailable(mock())
+
+ assertThat(latest).isTrue()
+ }
+
+ @Test
+ fun hasPrioritizedCaps_becomesFalseWhenNetworkLost() = runTest {
+ val latest by underTest.hasPrioritizedNetworkCapabilities.collectLastValue()
+
+ val callback: NetworkCallback =
+ argumentCaptor<NetworkCallback>()
+ .apply { verify(connectivityManager).registerNetworkCallback(any(), capture()) }
+ .lastValue
+
+ callback.onAvailable(mock())
+
+ assertThat(latest).isTrue()
+
+ callback.onLost(mock())
+
+ assertThat(latest).isFalse()
+ }
+
+ private inline fun <reified T> Kosmos.getTelephonyCallbackForType(): T {
+ return MobileTelephonyHelpers.getTelephonyCallbackForType(telephonyManager)
+ }
+
+ private fun carrierIdIntent(subId: Int = SUB_1_ID, carrierId: Int): Intent =
+ Intent(TelephonyManager.ACTION_SUBSCRIPTION_CARRIER_IDENTITY_CHANGED).apply {
+ putExtra(EXTRA_SUBSCRIPTION_ID, subId)
+ putExtra(EXTRA_CARRIER_ID, carrierId)
+ }
+
+ private fun spnIntent(
+ subId: Int = SUB_1_ID,
+ showSpn: Boolean = true,
+ spn: String? = SPN,
+ dataSpn: String? = DATA_SPN,
+ showPlmn: Boolean = true,
+ plmn: String? = PLMN,
+ ): Intent =
+ Intent(TelephonyManager.ACTION_SERVICE_PROVIDERS_UPDATED).apply {
+ putExtra(EXTRA_SUBSCRIPTION_INDEX, subId)
+ putExtra(EXTRA_SHOW_SPN, showSpn)
+ putExtra(EXTRA_SPN, spn)
+ putExtra(EXTRA_DATA_SPN, dataSpn)
+ putExtra(EXTRA_SHOW_PLMN, showPlmn)
+ putExtra(EXTRA_PLMN, plmn)
+ }
+
+ companion object {
+ private const val SUB_1_ID = 1
+
+ private const val DEFAULT_NAME = "Fake Mobile Network"
+ private val DEFAULT_NAME_MODEL = NetworkNameModel.Default(DEFAULT_NAME)
+ private const val SEP = "-"
+
+ private const val SPN = "testSpn"
+ private const val DATA_SPN = "testDataSpn"
+ private const val PLMN = "testPlmn"
+ }
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionsRepositoryKairosTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionsRepositoryKairosTest.kt
new file mode 100644
index 000000000000..e04a96eb3032
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionsRepositoryKairosTest.kt
@@ -0,0 +1,1350 @@
+/*
+ * 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.data.repository.prod
+
+import android.annotation.SuppressLint
+import android.content.Intent
+import android.content.applicationContext
+import android.content.testableContext
+import android.net.ConnectivityManager
+import android.net.Network
+import android.net.NetworkCapabilities
+import android.net.NetworkCapabilities.NET_CAPABILITY_VALIDATED
+import android.net.NetworkCapabilities.TRANSPORT_CELLULAR
+import android.net.NetworkCapabilities.TRANSPORT_ETHERNET
+import android.net.NetworkCapabilities.TRANSPORT_WIFI
+import android.net.connectivityManager
+import android.net.vcn.VcnTransportInfo
+import android.net.wifi.WifiInfo
+import android.net.wifi.WifiManager
+import android.os.ParcelUuid
+import android.telephony.CarrierConfigManager
+import android.telephony.ServiceState
+import android.telephony.SubscriptionInfo
+import android.telephony.SubscriptionManager
+import android.telephony.SubscriptionManager.INVALID_SUBSCRIPTION_ID
+import android.telephony.SubscriptionManager.OnSubscriptionsChangedListener
+import android.telephony.SubscriptionManager.PROFILE_CLASS_UNSET
+import android.telephony.TelephonyCallback.ActiveDataSubscriptionIdListener
+import android.telephony.TelephonyCallback.EmergencyCallbackModeListener
+import android.telephony.TelephonyManager
+import android.telephony.telephonyManager
+import android.testing.TestableLooper
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.internal.telephony.PhoneConstants
+import com.android.keyguard.KeyguardUpdateMonitorCallback
+import com.android.keyguard.keyguardUpdateMonitor
+import com.android.settingslib.R
+import com.android.settingslib.mobile.MobileMappings
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.broadcast.broadcastDispatcher
+import com.android.systemui.broadcast.broadcastDispatcherContext
+import com.android.systemui.flags.Flags
+import com.android.systemui.flags.fakeFeatureFlagsClassic
+import com.android.systemui.kairos.ExperimentalKairosApi
+import com.android.systemui.kairos.KairosTestScope
+import com.android.systemui.kairos.combine
+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.applicationCoroutineScope
+import com.android.systemui.kosmos.testDispatcher
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.log.LogBuffer
+import com.android.systemui.log.table.logcatTableLogBuffer
+import com.android.systemui.statusbar.connectivity.WifiPickerTrackerFactory
+import com.android.systemui.statusbar.pipeline.airplane.data.repository.airplaneModeRepository
+import com.android.systemui.statusbar.pipeline.mobile.data.model.SubscriptionModel
+import com.android.systemui.statusbar.pipeline.mobile.data.repository.MobileConnectionRepositoryKairos
+import com.android.systemui.statusbar.pipeline.mobile.data.repository.mobileConnectionsRepositoryKairosImpl
+import com.android.systemui.statusbar.pipeline.mobile.data.repository.prod.MobileTelephonyHelpers.getTelephonyCallbackForType
+import com.android.systemui.statusbar.pipeline.mobile.data.repository.subscriptionManager
+import com.android.systemui.statusbar.pipeline.mobile.data.repository.subscriptionManagerProxy
+import com.android.systemui.statusbar.pipeline.mobile.util.fake
+import com.android.systemui.statusbar.pipeline.shared.data.model.ConnectivitySlots
+import com.android.systemui.statusbar.pipeline.shared.data.repository.ConnectivityRepositoryImpl
+import com.android.systemui.statusbar.pipeline.shared.data.repository.connectivityRepository
+import com.android.systemui.statusbar.pipeline.wifi.data.repository.prod.WifiRepositoryImpl
+import com.android.systemui.statusbar.pipeline.wifi.data.repository.wifiRepository
+import com.android.systemui.testKosmos
+import com.android.systemui.user.data.repository.userRepository
+import com.android.systemui.util.concurrency.FakeExecutor
+import com.android.systemui.util.time.FakeSystemClock
+import com.android.wifitrackerlib.MergedCarrierEntry
+import com.android.wifitrackerlib.WifiEntry
+import com.android.wifitrackerlib.WifiPickerTracker
+import com.google.common.truth.Truth.assertThat
+import com.google.common.truth.Truth.assertWithMessage
+import java.util.UUID
+import kotlinx.coroutines.launch
+import kotlinx.coroutines.test.runCurrent
+import org.junit.Assert.assertTrue
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.ArgumentMatchers.anyInt
+import org.mockito.Mockito
+import org.mockito.Mockito.verify
+import org.mockito.kotlin.any
+import org.mockito.kotlin.argumentCaptor
+import org.mockito.kotlin.doAnswer
+import org.mockito.kotlin.doReturn
+import org.mockito.kotlin.mock
+import org.mockito.kotlin.stub
+import org.mockito.kotlin.whenever
+
+@OptIn(ExperimentalKairosApi::class)
+@SmallTest
+// This is required because our [SubscriptionManager.OnSubscriptionsChangedListener] uses a looper
+// to run the callback and this makes the looper place nicely with TestScope etc.
+@TestableLooper.RunWithLooper
+@RunWith(AndroidJUnit4::class)
+class MobileConnectionsRepositoryKairosTest : SysuiTestCase() {
+
+ private val Kosmos.wifiManager: WifiManager by Fixture { mock {} }
+ private val Kosmos.wifiPickerTrackerFactory: WifiPickerTrackerFactory by Fixture {
+ mock {
+ on { create(any(), any(), wifiPickerTrackerCallback.capture(), any()) } doReturn
+ wifiPickerTracker
+ }
+ }
+ private val Kosmos.wifiPickerTracker: WifiPickerTracker by Fixture { mock {} }
+ private val Kosmos.wifiTableLogBuffer by Fixture { logcatTableLogBuffer(this, "wifiTableLog") }
+
+ private val mainExecutor = FakeExecutor(FakeSystemClock())
+ private val wifiLogBuffer = LogBuffer("wifi", maxSize = 100, logcatEchoTracker = mock())
+ private val wifiPickerTrackerCallback =
+ argumentCaptor<WifiPickerTracker.WifiPickerTrackerCallback>()
+ private val vcnTransportInfo = VcnTransportInfo.Builder().build()
+
+ private val Kosmos.underTest
+ get() = mobileConnectionsRepositoryKairosImpl
+
+ private val kosmos =
+ testKosmos().apply {
+ fakeFeatureFlagsClassic.set(Flags.ROAMING_INDICATOR_VIA_DISPLAY_INFO, true)
+ broadcastDispatcherContext = testableContext
+ connectivityRepository =
+ ConnectivityRepositoryImpl(
+ connectivityManager,
+ ConnectivitySlots(applicationContext),
+ applicationContext,
+ mock(),
+ mock(),
+ applicationCoroutineScope,
+ mock(),
+ )
+ wifiRepository =
+ WifiRepositoryImpl(
+ applicationContext,
+ userRepository,
+ applicationCoroutineScope,
+ mainExecutor,
+ testDispatcher,
+ wifiPickerTrackerFactory,
+ wifiManager,
+ wifiLogBuffer,
+ wifiTableLogBuffer,
+ )
+ subscriptionManager.stub {
+ // For convenience, set up the subscription info callbacks
+ on { getActiveSubscriptionInfo(anyInt()) } doAnswer
+ { invocation ->
+ when (invocation.getArgument(0) as Int) {
+ 1 -> SUB_1
+ 2 -> SUB_2
+ 3 -> SUB_3
+ 4 -> SUB_4
+ else -> null
+ }
+ }
+ }
+ telephonyManager.stub {
+ on { simOperatorName } doReturn ""
+ // Set up so the individual connection repositories
+ on { createForSubscriptionId(anyInt()) } doAnswer
+ { invocation ->
+ telephonyManager.stub {
+ on { subscriptionId } doReturn invocation.getArgument(0)
+ }
+ }
+ }
+ testScope.runCurrent()
+ }
+
+ private fun runTest(block: suspend KairosTestScope.() -> Unit) =
+ kosmos.run { runKairosTest { block() } }
+
+ @Test
+ fun testSubscriptions_initiallyEmpty() = runTest {
+ assertThat(underTest.subscriptions.collectLastValue().value)
+ .isEqualTo(listOf<SubscriptionModel>())
+ }
+
+ @Test
+ fun testSubscriptions_listUpdates() = runTest {
+ val latest by underTest.subscriptions.collectLastValue()
+
+ subscriptionManager.stub {
+ on { completeActiveSubscriptionInfoList } doReturn listOf(SUB_1, SUB_2)
+ }
+ getSubscriptionCallback().onSubscriptionsChanged()
+
+ assertThat(latest).isEqualTo(listOf(MODEL_1, MODEL_2))
+ }
+
+ @Test
+ fun testSubscriptions_removingSub_updatesList() = runTest {
+ val latest by underTest.subscriptions.collectLastValue()
+
+ // WHEN 2 networks show up
+ subscriptionManager.stub {
+ on { completeActiveSubscriptionInfoList } doReturn listOf(SUB_1, SUB_2)
+ }
+ getSubscriptionCallback().onSubscriptionsChanged()
+
+ // WHEN one network is removed
+ subscriptionManager.stub {
+ on { completeActiveSubscriptionInfoList } doReturn listOf(SUB_2)
+ }
+ getSubscriptionCallback().onSubscriptionsChanged()
+
+ // THEN the subscriptions list represents the newest change
+ assertThat(latest).isEqualTo(listOf(MODEL_2))
+ }
+
+ @Test
+ fun subscriptions_subIsOnlyNtn_modelHasExclusivelyNtnTrue() = runTest {
+ val latest by underTest.subscriptions.collectLastValue()
+
+ val onlyNtnSub =
+ mock<SubscriptionInfo> {
+ on { isOnlyNonTerrestrialNetwork } doReturn true
+ on { subscriptionId } doReturn 45
+ on { groupUuid } doReturn GROUP_1
+ on { carrierName } doReturn "NTN only"
+ on { profileClass } doReturn PROFILE_CLASS_UNSET
+ }
+
+ subscriptionManager.stub {
+ on { completeActiveSubscriptionInfoList } doReturn listOf(onlyNtnSub)
+ }
+ getSubscriptionCallback().onSubscriptionsChanged()
+
+ assertThat(latest).hasSize(1)
+ assertThat(latest!![0].isExclusivelyNonTerrestrial).isTrue()
+ }
+
+ @Test
+ fun subscriptions_subIsNotOnlyNtn_modelHasExclusivelyNtnFalse() = runTest {
+ val latest by underTest.subscriptions.collectLastValue()
+
+ val notOnlyNtnSub =
+ mock<SubscriptionInfo> {
+ on { isOnlyNonTerrestrialNetwork } doReturn false
+ on { subscriptionId } doReturn 45
+ on { groupUuid } doReturn GROUP_1
+ on { carrierName } doReturn "NTN only"
+ on { profileClass } doReturn PROFILE_CLASS_UNSET
+ }
+
+ subscriptionManager.stub {
+ on { completeActiveSubscriptionInfoList } doReturn listOf(notOnlyNtnSub)
+ }
+ getSubscriptionCallback().onSubscriptionsChanged()
+
+ assertThat(latest).hasSize(1)
+ assertThat(latest!![0].isExclusivelyNonTerrestrial).isFalse()
+ }
+
+ @Test
+ fun testSubscriptions_carrierMergedOnly_listHasCarrierMerged() = runTest {
+ val latest by underTest.subscriptions.collectLastValue()
+
+ setWifiState(isCarrierMerged = true)
+ subscriptionManager.stub {
+ on { completeActiveSubscriptionInfoList } doReturn listOf(SUB_CM)
+ }
+ getSubscriptionCallback().onSubscriptionsChanged()
+
+ assertThat(latest).isEqualTo(listOf(MODEL_CM))
+ }
+
+ @Test
+ fun testSubscriptions_carrierMergedAndOther_listHasBothWithCarrierMergedLast() = runTest {
+ val latest by underTest.subscriptions.collectLastValue()
+
+ setWifiState(isCarrierMerged = true)
+ subscriptionManager.stub {
+ on { completeActiveSubscriptionInfoList } doReturn listOf(SUB_1, SUB_2, SUB_CM)
+ }
+ getSubscriptionCallback().onSubscriptionsChanged()
+
+ assertThat(latest).isEqualTo(listOf(MODEL_1, MODEL_2, MODEL_CM))
+ }
+
+ @Test
+ fun testActiveDataSubscriptionId_initialValueIsNull() = runTest {
+ assertThat(underTest.activeMobileDataSubscriptionId.collectLastValue().value)
+ .isEqualTo(null)
+ }
+
+ @Test
+ fun testActiveDataSubscriptionId_updates() = runTest {
+ val active by underTest.activeMobileDataSubscriptionId.collectLastValue()
+ testScope.runCurrent()
+
+ getTelephonyCallbackForType<ActiveDataSubscriptionIdListener>(telephonyManager)
+ .onActiveDataSubscriptionIdChanged(SUB_2_ID)
+
+ assertThat(active).isEqualTo(SUB_2_ID)
+ }
+
+ @Test
+ fun activeSubId_nullIfInvalidSubIdIsReceived() = runTest {
+ val latest by underTest.activeMobileDataSubscriptionId.collectLastValue()
+ testScope.runCurrent()
+
+ getTelephonyCallbackForType<ActiveDataSubscriptionIdListener>(telephonyManager)
+ .onActiveDataSubscriptionIdChanged(SUB_2_ID)
+
+ assertThat(latest).isNotNull()
+
+ getTelephonyCallbackForType<ActiveDataSubscriptionIdListener>(telephonyManager)
+ .onActiveDataSubscriptionIdChanged(INVALID_SUBSCRIPTION_ID)
+
+ assertThat(latest).isNull()
+ }
+
+ @Test
+ fun activeRepo_initiallyNull() = runTest {
+ assertThat(underTest.activeMobileDataRepository.collectLastValue().value).isNull()
+ }
+
+ @Test
+ fun activeRepo_updatesWithActiveDataId() = runTest {
+ val latest by underTest.activeMobileDataRepository.collectLastValue()
+ testScope.runCurrent()
+
+ // GIVEN the subscription list is then updated which includes the active data sub id
+ subscriptionManager.stub {
+ on { completeActiveSubscriptionInfoList } doReturn listOf(SUB_2)
+ }
+ getSubscriptionCallback().onSubscriptionsChanged()
+ testScope.runCurrent()
+
+ getTelephonyCallbackForType<ActiveDataSubscriptionIdListener>(telephonyManager)
+ .onActiveDataSubscriptionIdChanged(SUB_2_ID)
+
+ assertThat(latest?.subId).isEqualTo(SUB_2_ID)
+ }
+
+ @Test
+ fun activeRepo_nullIfActiveDataSubIdBecomesInvalid() = runTest {
+ val latest by underTest.activeMobileDataRepository.collectLastValue()
+ testScope.runCurrent()
+
+ // GIVEN the subscription list is then updated which includes the active data sub id
+ subscriptionManager.stub {
+ on { completeActiveSubscriptionInfoList } doReturn listOf(SUB_2)
+ }
+ getSubscriptionCallback().onSubscriptionsChanged()
+ testScope.runCurrent()
+
+ getTelephonyCallbackForType<ActiveDataSubscriptionIdListener>(telephonyManager)
+ .onActiveDataSubscriptionIdChanged(SUB_2_ID)
+ testScope.runCurrent()
+
+ assertThat(latest).isNotNull()
+
+ getTelephonyCallbackForType<ActiveDataSubscriptionIdListener>(telephonyManager)
+ .onActiveDataSubscriptionIdChanged(INVALID_SUBSCRIPTION_ID)
+ testScope.runCurrent()
+
+ assertThat(latest).isNull()
+ }
+
+ @Test
+ /** Regression test for b/268146648. */
+ fun activeSubIdIsSetBeforeSubscriptionsAreUpdated_doesNotThrow() = runTest {
+ val activeRepo by underTest.activeMobileDataRepository.collectLastValue()
+ val subscriptions by underTest.subscriptions.collectLastValue()
+ testScope.runCurrent()
+
+ getTelephonyCallbackForType<ActiveDataSubscriptionIdListener>(telephonyManager)
+ .onActiveDataSubscriptionIdChanged(SUB_2_ID)
+ testScope.runCurrent()
+
+ assertThat(subscriptions).isEmpty()
+ assertThat(activeRepo).isNull()
+ }
+
+ @Test
+ fun getRepoForSubId_activeDataSubIdIsRequestedBeforeSubscriptionsUpdate() = runTest {
+ underTest
+
+ var latestActiveRepo: MobileConnectionRepositoryKairos? = null
+ testScope.backgroundScope.launch {
+ kairos.activateSpec {
+ underTest.activeMobileDataSubscriptionId
+ .combine(underTest.mobileConnectionsBySubId) { id, conns ->
+ id?.let { conns[id] }
+ }
+ .observe {
+ if (it != null) {
+ latestActiveRepo = it
+ }
+ }
+ }
+ }
+
+ val latestSubscriptions by underTest.subscriptions.collectLastValue()
+ testScope.runCurrent()
+
+ // Active data subscription id is sent, but no subscription change has been posted yet
+ getTelephonyCallbackForType<ActiveDataSubscriptionIdListener>(telephonyManager)
+ .onActiveDataSubscriptionIdChanged(SUB_2_ID)
+ testScope.runCurrent()
+
+ // Subscriptions list is empty
+ assertThat(latestSubscriptions).isEmpty()
+
+ // getRepoForSubId does not throw
+ assertThat(latestActiveRepo).isNull()
+ }
+
+ @Test
+ fun activeDataSentBeforeSubscriptionList_subscriptionReusesActiveDataRepo() = runTest {
+ val activeRepo by underTest.activeMobileDataRepository.collectLastValue()
+ testScope.runCurrent()
+
+ // GIVEN active repo is updated before the subscription list updates
+ getTelephonyCallbackForType<ActiveDataSubscriptionIdListener>(telephonyManager)
+ .onActiveDataSubscriptionIdChanged(SUB_2_ID)
+ testScope.runCurrent()
+
+ assertThat(activeRepo).isNull()
+
+ // GIVEN the subscription list is then updated which includes the active data sub id
+ subscriptionManager.stub {
+ on { completeActiveSubscriptionInfoList } doReturn listOf(SUB_2)
+ }
+ getSubscriptionCallback().onSubscriptionsChanged()
+ testScope.runCurrent()
+
+ // WHEN requesting a connection repository for the subscription
+ val newRepo =
+ kairos.transact { underTest.mobileConnectionsBySubId.map { it[SUB_2_ID] }.sample() }
+
+ // THEN the newly request repo has been cached and reused
+ assertThat(activeRepo).isSameInstanceAs(newRepo)
+ }
+
+ @Test
+ fun testConnectionRepository_validSubId_isCached() = runTest {
+ underTest
+
+ subscriptionManager.stub {
+ on { completeActiveSubscriptionInfoList } doReturn listOf(SUB_1)
+ }
+ getSubscriptionCallback().onSubscriptionsChanged()
+
+ val repo1 by underTest.mobileConnectionsBySubId.map { it[SUB_1_ID] }.collectLastValue()
+ val repo2 by underTest.mobileConnectionsBySubId.map { it[SUB_1_ID] }.collectLastValue()
+
+ assertThat(repo1).isNotNull()
+ assertThat(repo1).isSameInstanceAs(repo2)
+ }
+
+ @Test
+ fun testConnectionRepository_carrierMergedSubId_isCached() = runTest {
+ underTest
+
+ getDefaultNetworkCallback().onCapabilitiesChanged(NETWORK, WIFI_NETWORK_CAPS_CM)
+ setWifiState(isCarrierMerged = true)
+ subscriptionManager.stub {
+ on { completeActiveSubscriptionInfoList } doReturn listOf(SUB_CM)
+ }
+ getSubscriptionCallback().onSubscriptionsChanged()
+
+ val repo1 by underTest.mobileConnectionsBySubId.map { it[SUB_CM_ID] }.collectLastValue()
+ val repo2 by underTest.mobileConnectionsBySubId.map { it[SUB_CM_ID] }.collectLastValue()
+
+ assertThat(repo1).isNotNull()
+ assertThat(repo1).isSameInstanceAs(repo2)
+ }
+
+ @SuppressLint("UnspecifiedRegisterReceiverFlag")
+ @Test
+ fun testDeviceEmergencyCallState_eagerlyChecksState() = runTest {
+ val latest by underTest.isDeviceEmergencyCallCapable.collectLastValue()
+
+ // Value starts out false
+ assertThat(latest).isFalse()
+ telephonyManager.stub { on { activeModemCount } doReturn 1 }
+ whenever(telephonyManager.getServiceStateForSlot(any())).thenAnswer { _ ->
+ ServiceState().apply { isEmergencyOnly = true }
+ }
+
+ // WHEN an appropriate intent gets sent out
+ val intent = serviceStateIntent(subId = -1)
+ broadcastDispatcher.sendIntentToMatchingReceiversOnly(applicationContext, intent)
+ testScope.runCurrent()
+
+ // THEN the repo's state is updated despite no listeners
+ assertThat(latest).isEqualTo(true)
+ }
+
+ @Test
+ fun testDeviceEmergencyCallState_aggregatesAcrossSlots_oneTrue() = runTest {
+ val latest by underTest.isDeviceEmergencyCallCapable.collectLastValue()
+
+ // GIVEN there are multiple slots
+ telephonyManager.stub { on { activeModemCount } doReturn 4 }
+ // GIVEN only one of them reports ECM
+ whenever(telephonyManager.getServiceStateForSlot(any())).thenAnswer { invocation ->
+ when (invocation.getArgument(0) as Int) {
+ 0 -> ServiceState().apply { isEmergencyOnly = false }
+ 1 -> ServiceState().apply { isEmergencyOnly = false }
+ 2 -> ServiceState().apply { isEmergencyOnly = true }
+ 3 -> ServiceState().apply { isEmergencyOnly = false }
+ else -> null
+ }
+ }
+
+ // GIVEN a broadcast goes out for the appropriate subID
+ val intent = serviceStateIntent(subId = -1)
+ broadcastDispatcher.sendIntentToMatchingReceiversOnly(applicationContext, intent)
+ testScope.runCurrent()
+
+ // THEN the device is in ECM, because one of the service states is
+ assertThat(latest).isTrue()
+ }
+
+ @Test
+ fun testDeviceEmergencyCallState_aggregatesAcrossSlots_allFalse() = runTest {
+ val latest by underTest.isDeviceEmergencyCallCapable.collectLastValue()
+
+ // GIVEN there are multiple slots
+ telephonyManager.stub { on { activeModemCount } doReturn 4 }
+ // GIVEN only one of them reports ECM
+ whenever(telephonyManager.getServiceStateForSlot(any())).thenAnswer { invocation ->
+ when (invocation.getArgument(0) as Int) {
+ 0 -> ServiceState().apply { isEmergencyOnly = false }
+ 1 -> ServiceState().apply { isEmergencyOnly = false }
+ 2 -> ServiceState().apply { isEmergencyOnly = false }
+ 3 -> ServiceState().apply { isEmergencyOnly = false }
+ else -> null
+ }
+ }
+
+ // GIVEN a broadcast goes out for the appropriate subID
+ val intent = serviceStateIntent(subId = -1)
+ broadcastDispatcher.sendIntentToMatchingReceiversOnly(applicationContext, intent)
+ testScope.runCurrent()
+
+ // THEN the device is in ECM, because one of the service states is
+ assertThat(latest).isFalse()
+ }
+
+ @Test
+ fun testConnectionCache_clearsInvalidSubscriptions() = runTest {
+ underTest
+
+ subscriptionManager.stub {
+ on { completeActiveSubscriptionInfoList } doReturn listOf(SUB_1, SUB_2)
+ }
+ getSubscriptionCallback().onSubscriptionsChanged()
+
+ val repoCache by underTest.mobileConnectionsBySubId.collectLastValue()
+
+ assertThat(repoCache?.keys).containsExactly(SUB_1_ID, SUB_2_ID)
+
+ // SUB_2 disappears
+ subscriptionManager.stub {
+ on { completeActiveSubscriptionInfoList } doReturn listOf(SUB_1)
+ }
+ getSubscriptionCallback().onSubscriptionsChanged()
+
+ assertThat(repoCache?.keys).containsExactly(SUB_1_ID)
+ }
+
+ @Test
+ fun testConnectionCache_clearsInvalidSubscriptions_includingCarrierMerged() = runTest {
+ underTest
+
+ getDefaultNetworkCallback().onCapabilitiesChanged(NETWORK, WIFI_NETWORK_CAPS_CM)
+ setWifiState(isCarrierMerged = true)
+ subscriptionManager.stub {
+ on { completeActiveSubscriptionInfoList } doReturn listOf(SUB_1, SUB_2, SUB_CM)
+ }
+ getSubscriptionCallback().onSubscriptionsChanged()
+
+ val repoCache by underTest.mobileConnectionsBySubId.collectLastValue()
+
+ assertThat(repoCache?.keys).containsExactly(SUB_1_ID, SUB_2_ID, SUB_CM_ID)
+
+ // SUB_2 and SUB_CM disappear
+ subscriptionManager.stub {
+ on { completeActiveSubscriptionInfoList } doReturn listOf(SUB_1)
+ }
+ getSubscriptionCallback().onSubscriptionsChanged()
+
+ assertThat(repoCache?.keys).containsExactly(SUB_1_ID)
+ }
+
+ /** Regression test for b/261706421 */
+ @Test
+ fun testConnectionsCache_clearMultipleSubscriptionsAtOnce_doesNotThrow() = runTest {
+ underTest
+
+ subscriptionManager.stub {
+ on { completeActiveSubscriptionInfoList } doReturn listOf(SUB_1, SUB_2)
+ }
+ getSubscriptionCallback().onSubscriptionsChanged()
+
+ val repoCache by underTest.mobileConnectionsBySubId.collectLastValue()
+
+ assertThat(repoCache?.keys).containsExactly(SUB_1_ID, SUB_2_ID)
+
+ // All subscriptions disappear
+ subscriptionManager.stub { on { completeActiveSubscriptionInfoList } doReturn listOf() }
+ getSubscriptionCallback().onSubscriptionsChanged()
+
+ assertThat(repoCache).isEmpty()
+ }
+
+ @Test
+ fun testDefaultDataSubId_updatesOnBroadcast() = runTest {
+ val latest by underTest.defaultDataSubId.collectLastValue()
+
+ assertThat(latest).isEqualTo(null)
+
+ val intent2 =
+ Intent(TelephonyManager.ACTION_DEFAULT_DATA_SUBSCRIPTION_CHANGED)
+ .putExtra(PhoneConstants.SUBSCRIPTION_KEY, SUB_2_ID)
+ broadcastDispatcher.sendIntentToMatchingReceiversOnly(applicationContext, intent2)
+
+ assertThat(latest).isEqualTo(SUB_2_ID)
+
+ val intent1 =
+ Intent(TelephonyManager.ACTION_DEFAULT_DATA_SUBSCRIPTION_CHANGED)
+ .putExtra(PhoneConstants.SUBSCRIPTION_KEY, SUB_1_ID)
+ broadcastDispatcher.sendIntentToMatchingReceiversOnly(applicationContext, intent1)
+
+ assertThat(latest).isEqualTo(SUB_1_ID)
+ }
+
+ @Test
+ fun defaultDataSubId_fetchesInitialValueOnStart() = runTest {
+ subscriptionManagerProxy.fake.defaultDataSubId = 2
+ val latest by underTest.defaultDataSubId.collectLastValue()
+
+ assertThat(latest).isEqualTo(2)
+ }
+
+ @Test
+ fun mobileIsDefault_startsAsFalse() = runTest {
+ assertThat(underTest.mobileIsDefault.collectLastValue().value).isFalse()
+ }
+
+ @Test
+ fun mobileIsDefault_capsHaveCellular_isDefault() = runTest {
+ val caps =
+ Mockito.mock(NetworkCapabilities::class.java).stub {
+ on { hasTransport(TRANSPORT_CELLULAR) } doReturn true
+ }
+
+ val latest by underTest.mobileIsDefault.collectLastValue()
+
+ getDefaultNetworkCallback().onCapabilitiesChanged(NETWORK, caps)
+
+ assertThat(latest).isTrue()
+ }
+
+ @Test
+ fun mobileIsDefault_capsDoNotHaveCellular_isNotDefault() = runTest {
+ val caps =
+ Mockito.mock(NetworkCapabilities::class.java).stub {
+ on { hasTransport(TRANSPORT_CELLULAR) } doReturn false
+ }
+
+ val latest by underTest.mobileIsDefault.collectLastValue()
+
+ getDefaultNetworkCallback().onCapabilitiesChanged(NETWORK, caps)
+
+ assertThat(latest).isFalse()
+ }
+
+ @Test
+ fun mobileIsDefault_carrierMergedViaMobile_isDefault() = runTest {
+ val carrierMergedInfo = mock<WifiInfo> { on { isCarrierMerged } doReturn true }
+ val caps =
+ Mockito.mock(NetworkCapabilities::class.java).stub {
+ on { hasTransport(TRANSPORT_CELLULAR) } doReturn true
+ on { transportInfo } doReturn carrierMergedInfo
+ }
+
+ val latest by underTest.mobileIsDefault.collectLastValue()
+
+ getDefaultNetworkCallback().onCapabilitiesChanged(NETWORK, caps)
+
+ assertThat(latest).isTrue()
+ }
+
+ @Test
+ fun mobileIsDefault_wifiDefault_mobileNotDefault() = runTest {
+ val caps =
+ Mockito.mock(NetworkCapabilities::class.java).stub {
+ on { hasTransport(TRANSPORT_WIFI) } doReturn true
+ }
+
+ val latest by underTest.mobileIsDefault.collectLastValue()
+
+ getDefaultNetworkCallback().onCapabilitiesChanged(NETWORK, caps)
+
+ assertThat(latest).isFalse()
+ }
+
+ @Test
+ fun mobileIsDefault_ethernetDefault_mobileNotDefault() = runTest {
+ val caps =
+ Mockito.mock(NetworkCapabilities::class.java).stub {
+ on { hasTransport(TRANSPORT_ETHERNET) } doReturn true
+ }
+
+ val latest by underTest.mobileIsDefault.collectLastValue()
+
+ getDefaultNetworkCallback().onCapabilitiesChanged(NETWORK, caps)
+
+ assertThat(latest).isFalse()
+ }
+
+ /** Regression test for b/272586234. */
+ @Test
+ fun hasCarrierMergedConnection_carrierMergedViaWifi_isTrue() = runTest {
+ val carrierMergedInfo =
+ mock<WifiInfo> {
+ on { isCarrierMerged } doReturn true
+ on { isPrimary } doReturn true
+ }
+ val caps =
+ Mockito.mock(NetworkCapabilities::class.java).stub {
+ on { hasTransport(TRANSPORT_WIFI) } doReturn true
+ on { transportInfo } doReturn carrierMergedInfo
+ }
+
+ val latest by underTest.hasCarrierMergedConnection.collectLastValue()
+
+ getDefaultNetworkCallback().onCapabilitiesChanged(NETWORK, caps)
+ setWifiState(isCarrierMerged = true)
+
+ assertThat(latest).isTrue()
+ }
+
+ @Test
+ fun hasCarrierMergedConnection_carrierMergedViaMobile_isTrue() = runTest {
+ val carrierMergedInfo =
+ mock<WifiInfo> {
+ on { isCarrierMerged } doReturn true
+ on { isPrimary } doReturn true
+ }
+ val caps =
+ Mockito.mock(NetworkCapabilities::class.java).stub {
+ on { hasTransport(TRANSPORT_CELLULAR) } doReturn true
+ on { transportInfo } doReturn carrierMergedInfo
+ }
+
+ val latest by underTest.hasCarrierMergedConnection.collectLastValue()
+
+ getDefaultNetworkCallback().onCapabilitiesChanged(NETWORK, caps)
+ setWifiState(isCarrierMerged = true)
+
+ assertThat(latest).isTrue()
+ }
+
+ private fun KairosTestScope.newWifiNetwork(wifiInfo: WifiInfo): Network {
+ val network = mock<Network>()
+ val capabilities =
+ Mockito.mock(NetworkCapabilities::class.java).stub {
+ on { hasTransport(TRANSPORT_WIFI) } doReturn true
+ on { transportInfo } doReturn wifiInfo
+ }
+ connectivityManager.stub { on { getNetworkCapabilities(network) } doReturn capabilities }
+ return network
+ }
+
+ /** Regression test for b/272586234. */
+ @Test
+ fun hasCarrierMergedConnection_carrierMergedViaWifiWithVcnTransport_isTrue() = runTest {
+ val carrierMergedInfo =
+ mock<WifiInfo> {
+ on { isCarrierMerged } doReturn true
+ on { isPrimary } doReturn true
+ }
+ val underlyingWifi = newWifiNetwork(carrierMergedInfo)
+ val caps =
+ Mockito.mock(NetworkCapabilities::class.java).stub {
+ on { hasTransport(TRANSPORT_WIFI) } doReturn true
+ on { transportInfo } doReturn vcnTransportInfo
+ on { underlyingNetworks } doReturn listOf(underlyingWifi)
+ }
+
+ val latest by underTest.hasCarrierMergedConnection.collectLastValue()
+
+ getDefaultNetworkCallback().onCapabilitiesChanged(NETWORK, caps)
+ setWifiState(isCarrierMerged = true)
+
+ assertThat(latest).isTrue()
+ }
+
+ @Test
+ fun hasCarrierMergedConnection_carrierMergedViaMobileWithVcnTransport_isTrue() = runTest {
+ val carrierMergedInfo =
+ mock<WifiInfo> {
+ on { isCarrierMerged } doReturn true
+ on { isPrimary } doReturn true
+ }
+ val underlyingWifi = newWifiNetwork(carrierMergedInfo)
+ val caps =
+ Mockito.mock(NetworkCapabilities::class.java).stub {
+ on { hasTransport(TRANSPORT_CELLULAR) } doReturn true
+ on { transportInfo } doReturn vcnTransportInfo
+ on { underlyingNetworks } doReturn listOf(underlyingWifi)
+ }
+
+ val latest by underTest.hasCarrierMergedConnection.collectLastValue()
+
+ getDefaultNetworkCallback().onCapabilitiesChanged(NETWORK, caps)
+ setWifiState(isCarrierMerged = true)
+
+ assertThat(latest).isTrue()
+ }
+
+ @Test
+ fun hasCarrierMergedConnection_isCarrierMergedViaUnderlyingWifi_isTrue() = runTest {
+ val latest by underTest.hasCarrierMergedConnection.collectLastValue()
+
+ val underlyingNetwork = mock<Network>()
+ val carrierMergedInfo =
+ mock<WifiInfo> {
+ on { isCarrierMerged } doReturn true
+ on { isPrimary } doReturn true
+ }
+ val underlyingWifiCapabilities =
+ Mockito.mock(NetworkCapabilities::class.java).stub {
+ on { hasTransport(TRANSPORT_WIFI) } doReturn true
+ on { transportInfo } doReturn carrierMergedInfo
+ }
+ connectivityManager.stub {
+ on { getNetworkCapabilities(underlyingNetwork) } doReturn underlyingWifiCapabilities
+ }
+
+ // WHEN the main capabilities have an underlying carrier merged network via WIFI
+ // transport and WifiInfo
+ val mainCapabilities =
+ Mockito.mock(NetworkCapabilities::class.java).stub {
+ on { hasTransport(TRANSPORT_CELLULAR) } doReturn true
+ on { transportInfo } doReturn null
+ on { underlyingNetworks } doReturn listOf(underlyingNetwork)
+ }
+
+ getDefaultNetworkCallback().onCapabilitiesChanged(NETWORK, mainCapabilities)
+ setWifiState(isCarrierMerged = true)
+
+ // THEN there's a carrier merged connection
+ assertThat(latest).isTrue()
+ }
+
+ @Test
+ fun hasCarrierMergedConnection_isCarrierMergedViaUnderlyingCellular_isTrue() = runTest {
+ val latest by underTest.hasCarrierMergedConnection.collectLastValue()
+
+ val underlyingCarrierMergedNetwork = mock<Network>()
+ val carrierMergedInfo =
+ mock<WifiInfo> {
+ on { isCarrierMerged } doReturn true
+ on { isPrimary } doReturn true
+ }
+
+ // The Wifi network that is under the VCN network
+ val physicalWifiNetwork = newWifiNetwork(carrierMergedInfo)
+
+ val underlyingCapabilities =
+ Mockito.mock(NetworkCapabilities::class.java).stub {
+ on { hasTransport(TRANSPORT_CELLULAR) } doReturn true
+ on { transportInfo } doReturn vcnTransportInfo
+ on { underlyingNetworks } doReturn listOf(physicalWifiNetwork)
+ }
+ connectivityManager.stub {
+ on { getNetworkCapabilities(underlyingCarrierMergedNetwork) } doReturn
+ underlyingCapabilities
+ }
+
+ // WHEN the main capabilities have an underlying carrier merged network via CELLULAR
+ // transport and VcnTransportInfo
+ val mainCapabilities =
+ Mockito.mock(NetworkCapabilities::class.java).stub {
+ on { hasTransport(TRANSPORT_CELLULAR) } doReturn true
+ on { transportInfo } doReturn null
+ on { underlyingNetworks } doReturn listOf(underlyingCarrierMergedNetwork)
+ }
+
+ getDefaultNetworkCallback().onCapabilitiesChanged(NETWORK, mainCapabilities)
+ setWifiState(isCarrierMerged = true)
+
+ // THEN there's a carrier merged connection
+ assertThat(latest).isTrue()
+ }
+
+ /** Regression test for b/272586234. */
+ @Test
+ fun hasCarrierMergedConnection_defaultIsWifiNotCarrierMerged_wifiRepoIsCarrierMerged_isTrue() =
+ runTest {
+ val latest by underTest.hasCarrierMergedConnection.collectLastValue()
+
+ // WHEN the default callback is TRANSPORT_WIFI but not carrier merged
+ val carrierMergedInfo = mock<WifiInfo> { on { isCarrierMerged } doReturn false }
+ val caps =
+ Mockito.mock(NetworkCapabilities::class.java).stub {
+ on { hasTransport(TRANSPORT_WIFI) } doReturn true
+ on { transportInfo } doReturn carrierMergedInfo
+ }
+ getDefaultNetworkCallback().onCapabilitiesChanged(NETWORK, caps)
+
+ // BUT the wifi repo has gotten updates that it *is* carrier merged
+ setWifiState(isCarrierMerged = true)
+
+ // THEN hasCarrierMergedConnection is true
+ assertThat(latest).isTrue()
+ }
+
+ /** Regression test for b/278618530. */
+ @Test
+ fun hasCarrierMergedConnection_defaultIsCellular_wifiRepoIsCarrierMerged_isFalse() = runTest {
+ val latest by underTest.hasCarrierMergedConnection.collectLastValue()
+
+ // WHEN the default callback is TRANSPORT_CELLULAR and not carrier merged
+ val caps =
+ Mockito.mock(NetworkCapabilities::class.java).stub {
+ on { hasTransport(TRANSPORT_CELLULAR) } doReturn true
+ on { transportInfo } doReturn null
+ }
+ getDefaultNetworkCallback().onCapabilitiesChanged(NETWORK, caps)
+
+ // BUT the wifi repo has gotten updates that it *is* carrier merged
+ setWifiState(isCarrierMerged = true)
+
+ // THEN hasCarrierMergedConnection is **false** (The default network being CELLULAR
+ // takes precedence over the wifi network being carrier merged.)
+ assertThat(latest).isFalse()
+ }
+
+ /** Regression test for b/278618530. */
+ @Test
+ fun hasCarrierMergedConnection_defaultCellular_wifiIsCarrierMerged_airplaneMode_isTrue() =
+ runTest {
+ val latest by underTest.hasCarrierMergedConnection.collectLastValue()
+
+ // WHEN the default callback is TRANSPORT_CELLULAR and not carrier merged
+ val caps =
+ Mockito.mock(NetworkCapabilities::class.java).stub {
+ on { hasTransport(TRANSPORT_CELLULAR) } doReturn true
+ on { transportInfo } doReturn null
+ }
+ getDefaultNetworkCallback().onCapabilitiesChanged(NETWORK, caps)
+
+ // BUT the wifi repo has gotten updates that it *is* carrier merged
+ setWifiState(isCarrierMerged = true)
+ // AND we're in airplane mode
+ airplaneModeRepository.setIsAirplaneMode(true)
+
+ // THEN hasCarrierMergedConnection is true.
+ assertThat(latest).isTrue()
+ }
+
+ @Test
+ fun defaultConnectionIsValidated_startsAsFalse() = runTest {
+ assertThat(underTest.defaultConnectionIsValidated.collectLastValue().value).isFalse()
+ }
+
+ @Test
+ fun defaultConnectionIsValidated_capsHaveValidated_isValidated() = runTest {
+ val caps =
+ Mockito.mock(NetworkCapabilities::class.java).stub {
+ on { hasCapability(NET_CAPABILITY_VALIDATED) } doReturn true
+ }
+
+ val latest by underTest.defaultConnectionIsValidated.collectLastValue()
+
+ getDefaultNetworkCallback().onCapabilitiesChanged(NETWORK, caps)
+
+ assertThat(latest).isTrue()
+ }
+
+ @Test
+ fun defaultConnectionIsValidated_capsHaveNotValidated_isNotValidated() = runTest {
+ val caps =
+ Mockito.mock(NetworkCapabilities::class.java).stub {
+ on { hasCapability(NET_CAPABILITY_VALIDATED) } doReturn false
+ }
+
+ val latest by underTest.defaultConnectionIsValidated.collectLastValue()
+
+ getDefaultNetworkCallback().onCapabilitiesChanged(NETWORK, caps)
+
+ assertThat(latest).isFalse()
+ }
+
+ @Test
+ fun config_initiallyFromContext() = runTest {
+ overrideResource(R.bool.config_showMin3G, true)
+ val configFromContext = MobileMappings.Config.readConfig(applicationContext)
+ assertThat(configFromContext.showAtLeast3G).isTrue()
+
+ val latest by underTest.defaultDataSubRatConfig.collectLastValue()
+
+ assertTrue(latest!!.areEqual(configFromContext))
+ assertTrue(latest!!.showAtLeast3G)
+ }
+
+ @Test
+ fun config_subIdChangeEvent_updated() = runTest {
+ val latest by underTest.defaultDataSubRatConfig.collectLastValue()
+
+ assertThat(latest!!.showAtLeast3G).isFalse()
+
+ overrideResource(R.bool.config_showMin3G, true)
+ val configFromContext = MobileMappings.Config.readConfig(applicationContext)
+ assertThat(configFromContext.showAtLeast3G).isTrue()
+
+ // WHEN the change event is fired
+ val intent =
+ Intent(TelephonyManager.ACTION_DEFAULT_DATA_SUBSCRIPTION_CHANGED)
+ .putExtra(PhoneConstants.SUBSCRIPTION_KEY, SUB_1_ID)
+ broadcastDispatcher.sendIntentToMatchingReceiversOnly(applicationContext, intent)
+
+ // THEN the config is updated
+ assertThat(latest?.areEqual(configFromContext)).isEqualTo(true)
+ assertThat(latest?.showAtLeast3G).isEqualTo(true)
+ }
+
+ @Test
+ fun config_carrierConfigChangeEvent_updated() = runTest {
+ val latest by underTest.defaultDataSubRatConfig.collectLastValue()
+
+ assertThat(latest!!.showAtLeast3G).isFalse()
+
+ overrideResource(R.bool.config_showMin3G, true)
+ val configFromContext = MobileMappings.Config.readConfig(applicationContext)
+ assertThat(configFromContext.showAtLeast3G).isTrue()
+
+ // WHEN the change event is fired
+ broadcastDispatcher.sendIntentToMatchingReceiversOnly(
+ applicationContext,
+ Intent(CarrierConfigManager.ACTION_CARRIER_CONFIG_CHANGED),
+ )
+
+ // THEN the config is updated
+ assertThat(latest?.areEqual(configFromContext)).isEqualTo(true)
+ assertThat(latest?.showAtLeast3G).isEqualTo(true)
+ }
+
+ @Test
+ fun carrierConfig_initialValueIsFetched() = runTest {
+ underTest
+ testScope.runCurrent()
+
+ // Value starts out false
+ assertThat(underTest.defaultDataSubRatConfig.sample().showAtLeast3G).isFalse()
+
+ overrideResource(R.bool.config_showMin3G, true)
+ val configFromContext = MobileMappings.Config.readConfig(applicationContext)
+ assertThat(configFromContext.showAtLeast3G).isTrue()
+
+ assertThat(broadcastDispatcher.numReceiversRegistered).isAtLeast(1)
+
+ // WHEN the change event is fired
+ broadcastDispatcher.sendIntentToMatchingReceiversOnly(
+ applicationContext,
+ Intent(CarrierConfigManager.ACTION_CARRIER_CONFIG_CHANGED),
+ )
+ testScope.runCurrent()
+
+ // WHEN collection starts AFTER the broadcast is sent out
+ val latest by underTest.defaultDataSubRatConfig.collectLastValue()
+
+ // THEN the config has the updated value
+ assertWithMessage("showAtLeast3G is false").that(latest!!.showAtLeast3G).isTrue()
+ assertWithMessage("not equal").that(latest!!.areEqual(configFromContext)).isTrue()
+ }
+
+ @Test
+ fun activeDataChange_inSameGroup_emitsUnit() = runTest {
+ var eventCount = 0
+ underTest
+ testScope.backgroundScope.launch {
+ kairos.activateSpec { underTest.activeSubChangedInGroupEvent.observe { eventCount++ } }
+ }
+ testScope.runCurrent()
+
+ getTelephonyCallbackForType<ActiveDataSubscriptionIdListener>(telephonyManager)
+ .onActiveDataSubscriptionIdChanged(SUB_3_ID_GROUPED)
+ testScope.runCurrent()
+
+ getTelephonyCallbackForType<ActiveDataSubscriptionIdListener>(telephonyManager)
+ .onActiveDataSubscriptionIdChanged(SUB_4_ID_GROUPED)
+ testScope.runCurrent()
+
+ assertThat(eventCount).isEqualTo(1)
+ }
+
+ @Test
+ fun activeDataChange_notInSameGroup_doesNotEmit() = runTest {
+ var eventCount = 0
+ underTest
+ testScope.backgroundScope.launch {
+ kairos.activateSpec { underTest.activeSubChangedInGroupEvent.observe { eventCount++ } }
+ }
+ testScope.runCurrent()
+
+ getTelephonyCallbackForType<ActiveDataSubscriptionIdListener>(telephonyManager)
+ .onActiveDataSubscriptionIdChanged(SUB_3_ID_GROUPED)
+ testScope.runCurrent()
+
+ getTelephonyCallbackForType<ActiveDataSubscriptionIdListener>(telephonyManager)
+ .onActiveDataSubscriptionIdChanged(SUB_1_ID)
+ testScope.runCurrent()
+
+ assertThat(eventCount).isEqualTo(0)
+ }
+
+ @Test
+ fun anySimSecure_propagatesStateFromKeyguardUpdateMonitor() = runTest {
+ val latest by underTest.isAnySimSecure.collectLastValue()
+ assertThat(latest).isFalse()
+
+ val updateMonitorCallback = argumentCaptor<KeyguardUpdateMonitorCallback>()
+ verify(keyguardUpdateMonitor).registerCallback(updateMonitorCallback.capture())
+
+ keyguardUpdateMonitor.stub { on { isSimPinSecure } doReturn true }
+ updateMonitorCallback.lastValue.onSimStateChanged(0, 0, 0)
+
+ assertThat(latest).isTrue()
+
+ keyguardUpdateMonitor.stub { on { isSimPinSecure } doReturn false }
+ updateMonitorCallback.lastValue.onSimStateChanged(0, 0, 0)
+
+ assertThat(latest).isFalse()
+ }
+
+ @Test
+ fun getIsAnySimSecure_delegatesCallToKeyguardUpdateMonitor() = runTest {
+ val anySimSecure by underTest.isAnySimSecure.collectLastValue()
+
+ assertThat(anySimSecure).isFalse()
+
+ keyguardUpdateMonitor.stub { on { isSimPinSecure } doReturn true }
+ argumentCaptor<KeyguardUpdateMonitorCallback>()
+ .apply { verify(keyguardUpdateMonitor).registerCallback(capture()) }
+ .lastValue
+ .onSimStateChanged(0, 0, 0)
+
+ assertThat(anySimSecure).isTrue()
+ }
+
+ @Test
+ fun noSubscriptionsInEcmMode_notInEcmMode() = runTest {
+ val latest by underTest.isInEcmMode.collectLastValue()
+ testScope.runCurrent()
+
+ assertThat(latest).isFalse()
+ }
+
+ @Test
+ fun someSubscriptionsInEcmMode_inEcmMode() = runTest {
+ val latest by underTest.isInEcmMode.collectLastValue()
+ testScope.runCurrent()
+
+ getTelephonyCallbackForType<EmergencyCallbackModeListener>(telephonyManager)
+ .onCallbackModeStarted(0, mock(), 0)
+
+ assertThat(latest).isTrue()
+ }
+
+ private fun KairosTestScope.getDefaultNetworkCallback(): ConnectivityManager.NetworkCallback {
+ testScope.runCurrent()
+ val callbackCaptor = argumentCaptor<ConnectivityManager.NetworkCallback>()
+ verify(connectivityManager).registerDefaultNetworkCallback(callbackCaptor.capture())
+ return callbackCaptor.lastValue
+ }
+
+ private fun KairosTestScope.setWifiState(isCarrierMerged: Boolean) {
+ if (isCarrierMerged) {
+ val mergedEntry =
+ mock<MergedCarrierEntry> {
+ on { isPrimaryNetwork } doReturn true
+ on { isDefaultNetwork } doReturn true
+ on { subscriptionId } doReturn SUB_CM_ID
+ }
+ wifiPickerTracker.stub {
+ on { mergedCarrierEntry } doReturn mergedEntry
+ on { connectedWifiEntry } doReturn null
+ }
+ } else {
+ val wifiEntry =
+ mock<WifiEntry> {
+ on { isPrimaryNetwork } doReturn true
+ on { isDefaultNetwork } doReturn true
+ }
+ wifiPickerTracker.stub {
+ on { connectedWifiEntry } doReturn wifiEntry
+ on { mergedCarrierEntry } doReturn null
+ }
+ }
+ wifiPickerTrackerCallback.allValues.forEach { it.onWifiEntriesChanged() }
+ }
+
+ private fun KairosTestScope.getSubscriptionCallback(): OnSubscriptionsChangedListener {
+ testScope.runCurrent()
+ return argumentCaptor<OnSubscriptionsChangedListener>()
+ .apply {
+ verify(subscriptionManager).addOnSubscriptionsChangedListener(any(), capture())
+ }
+ .lastValue
+ }
+
+ companion object {
+ // Subscription 1
+ private const val SUB_1_ID = 1
+ private const val SUB_1_NAME = "Carrier $SUB_1_ID"
+ private val GROUP_1 = ParcelUuid(UUID.randomUUID())
+ private val SUB_1 =
+ mock<SubscriptionInfo> {
+ on { subscriptionId } doReturn SUB_1_ID
+ on { groupUuid } doReturn GROUP_1
+ on { carrierName } doReturn SUB_1_NAME
+ on { profileClass } doReturn PROFILE_CLASS_UNSET
+ }
+ private val MODEL_1 =
+ SubscriptionModel(
+ subscriptionId = SUB_1_ID,
+ groupUuid = GROUP_1,
+ carrierName = SUB_1_NAME,
+ profileClass = PROFILE_CLASS_UNSET,
+ )
+
+ // Subscription 2
+ private const val SUB_2_ID = 2
+ private const val SUB_2_NAME = "Carrier $SUB_2_ID"
+ private val GROUP_2 = ParcelUuid(UUID.randomUUID())
+ private val SUB_2 =
+ mock<SubscriptionInfo> {
+ on { subscriptionId } doReturn SUB_2_ID
+ on { groupUuid } doReturn GROUP_2
+ on { carrierName } doReturn SUB_2_NAME
+ on { profileClass } doReturn PROFILE_CLASS_UNSET
+ }
+ private val MODEL_2 =
+ SubscriptionModel(
+ subscriptionId = SUB_2_ID,
+ groupUuid = GROUP_2,
+ carrierName = SUB_2_NAME,
+ profileClass = PROFILE_CLASS_UNSET,
+ )
+
+ // Subs 3 and 4 are considered to be in the same group ------------------------------------
+ private val GROUP_ID_3_4 = ParcelUuid(UUID.randomUUID())
+
+ // Subscription 3
+ private const val SUB_3_ID_GROUPED = 3
+ private val SUB_3 =
+ mock<SubscriptionInfo> {
+ on { subscriptionId } doReturn SUB_3_ID_GROUPED
+ on { groupUuid } doReturn GROUP_ID_3_4
+ on { profileClass } doReturn PROFILE_CLASS_UNSET
+ }
+
+ // Subscription 4
+ private const val SUB_4_ID_GROUPED = 4
+ private val SUB_4 =
+ mock<SubscriptionInfo> {
+ on { subscriptionId } doReturn SUB_4_ID_GROUPED
+ on { groupUuid } doReturn GROUP_ID_3_4
+ on { profileClass } doReturn PROFILE_CLASS_UNSET
+ }
+
+ // Subs 3 and 4 are considered to be in the same group ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+ private const val NET_ID = 123
+ private val NETWORK = mock<Network> { on { getNetId() } doReturn NET_ID }
+
+ // Carrier merged subscription
+ private const val SUB_CM_ID = 5
+ private const val SUB_CM_NAME = "Carrier $SUB_CM_ID"
+ private val SUB_CM =
+ mock<SubscriptionInfo> {
+ on { subscriptionId } doReturn SUB_CM_ID
+ on { carrierName } doReturn SUB_CM_NAME
+ on { profileClass } doReturn PROFILE_CLASS_UNSET
+ }
+ private val MODEL_CM =
+ SubscriptionModel(
+ subscriptionId = SUB_CM_ID,
+ carrierName = SUB_CM_NAME,
+ profileClass = PROFILE_CLASS_UNSET,
+ )
+
+ private val WIFI_INFO_CM =
+ mock<WifiInfo> {
+ on { isPrimary } doReturn true
+ on { isCarrierMerged } doReturn true
+ on { subscriptionId } doReturn SUB_CM_ID
+ }
+ private val WIFI_NETWORK_CAPS_CM =
+ Mockito.mock(NetworkCapabilities::class.java).stub {
+ on { hasTransport(TRANSPORT_WIFI) } doReturn true
+ on { transportInfo } doReturn WIFI_INFO_CM
+ on { hasCapability(NET_CAPABILITY_VALIDATED) } doReturn true
+ }
+
+ private val WIFI_INFO_ACTIVE =
+ mock<WifiInfo> {
+ on { isPrimary } doReturn true
+ on { isCarrierMerged } doReturn false
+ }
+
+ private val WIFI_NETWORK_CAPS_ACTIVE =
+ Mockito.mock(NetworkCapabilities::class.java).stub {
+ on { hasTransport(TRANSPORT_WIFI) } doReturn true
+ on { transportInfo } doReturn WIFI_INFO_ACTIVE
+ on { hasCapability(NET_CAPABILITY_VALIDATED) } doReturn true
+ }
+
+ /**
+ * To properly mimic telephony manager, create a service state, and then turn it into an
+ * intent
+ */
+ private fun serviceStateIntent(subId: Int): Intent {
+ return Intent(Intent.ACTION_SERVICE_STATE).apply {
+ putExtra(SubscriptionManager.EXTRA_SUBSCRIPTION_INDEX, subId)
+ }
+ }
+ }
+}
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
new file mode 100644
index 000000000000..33689931e6ed
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconInteractorKairosTest.kt
@@ -0,0 +1,760 @@
+/*
+ * 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.platform.test.annotations.DisableFlags
+import android.platform.test.annotations.EnableFlags
+import android.telephony.CellSignalStrength
+import android.telephony.TelephonyManager.NETWORK_TYPE_UNKNOWN
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+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.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.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.testKosmos
+import com.google.common.truth.Truth.assertThat
+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().apply {
+ useUnconfinedTestDispatcher()
+ featureFlagsClassic.fake.apply { setDefault(FILTER_PROVISIONING_NETWORK_SUBSCRIPTIONS) }
+ }
+
+ private val Kosmos.tableLogBuffer by Fixture {
+ logcatTableLogBuffer(this, "MobileIconInteractorKairosTest")
+ }
+
+ private var Kosmos.overrides: MobileIconCarrierIdOverrides by Fixture {
+ MobileIconCarrierIdOverridesImpl()
+ }
+
+ private val Kosmos.defaultSubscriptionHasDataEnabled by Fixture { MutableState(kairos, true) }
+
+ private val Kosmos.alwaysShowDataRatIcon by Fixture { MutableState(kairos, false) }
+
+ private val Kosmos.alwaysUseCdmaLevel by Fixture { MutableState(kairos, false) }
+
+ private val Kosmos.isSingleCarrier by Fixture { MutableState(kairos, true) }
+
+ private val Kosmos.mobileIsDefault by Fixture { MutableState(kairos, false) }
+
+ private val Kosmos.defaultMobileIconMapping by Fixture {
+ MutableState(kairos, fakeMobileIconsInteractor.TEST_MAPPING)
+ }
+
+ private val Kosmos.defaultMobileIconGroup by Fixture { MutableState(kairos, TelephonyIcons.G) }
+
+ private val Kosmos.isDefaultConnectionFailed by Fixture { MutableState(kairos, false) }
+
+ private val Kosmos.isForceHidden by Fixture { MutableState(kairos, false) }
+
+ private val Kosmos.underTest by ActivatedKairosFixture {
+ MobileIconInteractorKairosImpl(
+ defaultSubscriptionHasDataEnabled,
+ alwaysShowDataRatIcon,
+ alwaysUseCdmaLevel,
+ isSingleCarrier,
+ mobileIsDefault,
+ defaultMobileIconMapping,
+ defaultMobileIconGroup,
+ isDefaultConnectionFailed,
+ isForceHidden,
+ connectionRepository = connectionRepo,
+ context = context,
+ carrierIdOverrides = overrides,
+ )
+ }
+
+ 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 gsm_usesGsmLevel() = runTest {
+ connectionRepo.isGsm.setValue(true)
+ connectionRepo.primaryLevel.setValue(GSM_LEVEL)
+ connectionRepo.cdmaLevel.setValue(CDMA_LEVEL)
+
+ val latest by underTest.signalLevelIcon.collectLastValue()
+
+ assertThat(latest?.level).isEqualTo(GSM_LEVEL)
+ }
+
+ @Test
+ 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)
+
+ val latest by underTest.signalLevelIcon.collectLastValue()
+
+ assertThat(latest?.level).isEqualTo(GSM_LEVEL)
+ }
+
+ @Test
+ fun notGsm_level_default_unknown() = runTest {
+ connectionRepo.isGsm.setValue(false)
+
+ val latest by underTest.signalLevelIcon.collectLastValue()
+
+ assertThat(latest?.level).isEqualTo(CellSignalStrength.SIGNAL_STRENGTH_NONE_OR_UNKNOWN)
+ }
+
+ @Test
+ 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)
+
+ val latest by underTest.signalLevelIcon.collectLastValue()
+
+ assertThat(latest?.level).isEqualTo(CDMA_LEVEL)
+ }
+
+ @Test
+ 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)
+
+ val latest by underTest.signalLevelIcon.collectLastValue()
+
+ assertThat(latest?.level).isEqualTo(GSM_LEVEL)
+ }
+
+ @Test
+ fun numberOfLevels_comesFromRepo_whenApplicable() = runTest {
+ val latest by
+ underTest.signalLevelIcon
+ .map { (it as? SignalIconModel.Cellular)?.numberOfLevels }
+ .collectLastValue()
+
+ connectionRepo.numberOfLevels.setValue(5)
+ assertThat(latest).isEqualTo(5)
+
+ connectionRepo.numberOfLevels.setValue(4)
+ assertThat(latest).isEqualTo(4)
+ }
+
+ @Test
+ fun inflateSignalStrength_arbitrarilyAddsOneToTheReportedLevel() = runTest {
+ connectionRepo.inflateSignalStrength.setValue(false)
+ val latest by underTest.signalLevelIcon.collectLastValue()
+
+ connectionRepo.primaryLevel.setValue(4)
+ assertThat(latest!!.level).isEqualTo(4)
+
+ 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_configOn_hasPrioritizedCaps_showsSlice() = runTest {
+ connectionRepo.allowNetworkSliceIndicator.setValue(true)
+ val latest by underTest.showSliceAttribution.collectLastValue()
+
+ connectionRepo.hasPrioritizedNetworkCapabilities.setValue(true)
+
+ assertThat(latest).isTrue()
+ }
+
+ @Test
+ fun networkSlice_configOn_noPrioritizedCaps_noSlice() = runTest {
+ connectionRepo.allowNetworkSliceIndicator.setValue(true)
+ val latest by underTest.showSliceAttribution.collectLastValue()
+
+ connectionRepo.hasPrioritizedNetworkCapabilities.setValue(false)
+
+ assertThat(latest).isFalse()
+ }
+
+ @Test
+ fun networkSlice_configOff_hasPrioritizedCaps_noSlice() = runTest {
+ connectionRepo.allowNetworkSliceIndicator.setValue(false)
+ val latest by underTest.showSliceAttribution.collectLastValue()
+
+ connectionRepo.hasPrioritizedNetworkCapabilities.setValue(true)
+
+ assertThat(latest).isFalse()
+ }
+
+ @Test
+ fun networkSlice_configOff_noPrioritizedCaps_noSlice() = runTest {
+ connectionRepo.allowNetworkSliceIndicator.setValue(false)
+ val latest by underTest.showSliceAttribution.collectLastValue()
+
+ connectionRepo.hasPrioritizedNetworkCapabilities.setValue(false)
+
+ assertThat(latest).isFalse()
+ }
+
+ @Test
+ fun iconGroup_three_g() = runTest {
+ connectionRepo.resolvedNetworkType.setValue(
+ DefaultNetworkType(mobileMappingsProxy.toIconKey(THREE_G))
+ )
+
+ val latest by underTest.networkTypeIconGroup.collectLastValue()
+
+ assertThat(latest).isEqualTo(NetworkTypeIconModel.DefaultIcon(TelephonyIcons.THREE_G))
+ }
+
+ @Test
+ fun iconGroup_updates_on_change() = runTest {
+ connectionRepo.resolvedNetworkType.setValue(
+ DefaultNetworkType(mobileMappingsProxy.toIconKey(THREE_G))
+ )
+
+ val latest by underTest.networkTypeIconGroup.collectLastValue()
+
+ connectionRepo.resolvedNetworkType.setValue(
+ DefaultNetworkType(mobileMappingsProxy.toIconKey(FOUR_G))
+ )
+
+ assertThat(latest).isEqualTo(NetworkTypeIconModel.DefaultIcon(TelephonyIcons.FOUR_G))
+ }
+
+ @Test
+ fun iconGroup_5g_override_type() = runTest {
+ connectionRepo.resolvedNetworkType.setValue(
+ OverrideNetworkType(mobileMappingsProxy.toIconKeyOverride(FIVE_G_OVERRIDE))
+ )
+
+ val latest by underTest.networkTypeIconGroup.collectLastValue()
+
+ assertThat(latest).isEqualTo(NetworkTypeIconModel.DefaultIcon(TelephonyIcons.NR_5G))
+ }
+
+ @Test
+ fun iconGroup_default_if_no_lookup() = runTest {
+ connectionRepo.resolvedNetworkType.setValue(
+ DefaultNetworkType(mobileMappingsProxy.toIconKey(NETWORK_TYPE_UNKNOWN))
+ )
+
+ val latest by underTest.networkTypeIconGroup.collectLastValue()
+
+ assertThat(latest)
+ .isEqualTo(NetworkTypeIconModel.DefaultIcon(FakeMobileIconsInteractor.DEFAULT_ICON))
+ }
+
+ @Test
+ fun iconGroup_carrierMerged_usesOverride() = runTest {
+ connectionRepo.resolvedNetworkType.setValue(CarrierMergedNetworkType)
+
+ val latest by underTest.networkTypeIconGroup.collectLastValue()
+
+ assertThat(latest)
+ .isEqualTo(NetworkTypeIconModel.DefaultIcon(CarrierMergedNetworkType.iconGroupOverride))
+ }
+
+ @Test
+ fun overrideIcon_usesCarrierIdOverride() = runTest {
+ overrides =
+ mock<MobileIconCarrierIdOverrides> {
+ on { carrierIdEntryExists(anyInt()) } doReturn true
+ on { getOverrideFor(anyInt(), anyString(), any()) } doReturn 1234
+ }
+
+ connectionRepo.resolvedNetworkType.setValue(
+ DefaultNetworkType(mobileMappingsProxy.toIconKey(THREE_G))
+ )
+
+ val latest by underTest.networkTypeIconGroup.collectLastValue()
+
+ assertThat(latest)
+ .isEqualTo(NetworkTypeIconModel.OverriddenIcon(TelephonyIcons.THREE_G, 1234))
+ }
+
+ @Test
+ fun alwaysShowDataRatIcon_matchesParent() = runTest {
+ val latest by underTest.alwaysShowDataRatIcon.collectLastValue()
+
+ // mobileIconsInteractor.alwaysShowDataRatIcon.setValue(true)
+ alwaysShowDataRatIcon.setValue(true)
+
+ assertThat(latest).isTrue()
+
+ // mobileIconsInteractor.alwaysShowDataRatIcon.setValue(false)
+ alwaysShowDataRatIcon.setValue(false)
+
+ assertThat(latest).isFalse()
+ }
+
+ @Test
+ fun dataState_connected() = runTest {
+ val latest by underTest.isDataConnected.collectLastValue()
+
+ connectionRepo.dataConnectionState.setValue(DataConnectionState.Connected)
+
+ assertThat(latest).isTrue()
+ }
+
+ @Test
+ fun dataState_notConnected() = runTest {
+ val latest by underTest.isDataConnected.collectLastValue()
+
+ connectionRepo.dataConnectionState.setValue(DataConnectionState.Disconnected)
+
+ assertThat(latest).isFalse()
+ }
+
+ @Test
+ fun isInService_usesRepositoryValue() = runTest {
+ val latest by underTest.isInService.collectLastValue()
+
+ connectionRepo.isInService.setValue(true)
+
+ assertThat(latest).isTrue()
+
+ connectionRepo.isInService.setValue(false)
+
+ assertThat(latest).isFalse()
+ }
+
+ @Test
+ fun roaming_isGsm_usesConnectionModel() = runTest {
+ val latest by underTest.isRoaming.collectLastValue()
+
+ connectionRepo.cdmaRoaming.setValue(true)
+ connectionRepo.isGsm.setValue(true)
+ connectionRepo.isRoaming.setValue(false)
+
+ assertThat(latest).isFalse()
+
+ connectionRepo.isRoaming.setValue(true)
+
+ assertThat(latest).isTrue()
+ }
+
+ @Test
+ fun roaming_isCdma_usesCdmaRoamingBit() = runTest {
+ val latest by underTest.isRoaming.collectLastValue()
+
+ connectionRepo.cdmaRoaming.setValue(false)
+ connectionRepo.isGsm.setValue(false)
+ connectionRepo.isRoaming.setValue(true)
+
+ assertThat(latest).isFalse()
+
+ connectionRepo.cdmaRoaming.setValue(true)
+ connectionRepo.isGsm.setValue(false)
+ connectionRepo.isRoaming.setValue(false)
+
+ assertThat(latest).isTrue()
+ }
+
+ @Test
+ fun roaming_falseWhileCarrierNetworkChangeActive() = runTest {
+ val latest by underTest.isRoaming.collectLastValue()
+
+ connectionRepo.cdmaRoaming.setValue(true)
+ connectionRepo.isGsm.setValue(false)
+ connectionRepo.isRoaming.setValue(true)
+ connectionRepo.carrierNetworkChangeActive.setValue(true)
+
+ assertThat(latest).isFalse()
+
+ connectionRepo.cdmaRoaming.setValue(true)
+ connectionRepo.isGsm.setValue(true)
+
+ assertThat(latest).isFalse()
+ }
+
+ @Test
+ fun networkName_usesOperatorAlphaShortWhenNonNullAndRepoIsDefault() = runTest {
+ val latest by underTest.networkName.collectLastValue()
+
+ val testOperatorName = "operatorAlphaShort"
+
+ // Default network name, operator name is non-null, uses the operator name
+ connectionRepo.networkName.setValue(DEFAULT_NAME_MODEL)
+ connectionRepo.operatorAlphaShort.setValue(testOperatorName)
+
+ assertThat(latest).isEqualTo(NetworkNameModel.IntentDerived(testOperatorName))
+
+ // Default network name, operator name is null, uses the default
+ connectionRepo.operatorAlphaShort.setValue(null)
+
+ assertThat(latest).isEqualTo(DEFAULT_NAME_MODEL)
+
+ // Derived network name, operator name non-null, uses the derived name
+ connectionRepo.networkName.setValue(DERIVED_NAME_MODEL)
+ connectionRepo.operatorAlphaShort.setValue(testOperatorName)
+
+ assertThat(latest).isEqualTo(DERIVED_NAME_MODEL)
+ }
+
+ @Test
+ fun networkNameForSubId_usesOperatorAlphaShortWhenNonNullAndRepoIsDefault() = runTest {
+ val latest by underTest.carrierName.collectLastValue()
+
+ val testOperatorName = "operatorAlphaShort"
+
+ // Default network name, operator name is non-null, uses the operator name
+ connectionRepo.carrierName.setValue(DEFAULT_NAME_MODEL)
+ connectionRepo.operatorAlphaShort.setValue(testOperatorName)
+
+ assertThat(latest).isEqualTo(testOperatorName)
+
+ // Default network name, operator name is null, uses the default
+ connectionRepo.operatorAlphaShort.setValue(null)
+
+ assertThat(latest).isEqualTo(DEFAULT_NAME)
+
+ // Derived network name, operator name non-null, uses the derived name
+ connectionRepo.carrierName.setValue(NetworkNameModel.SubscriptionDerived(DERIVED_NAME))
+ connectionRepo.operatorAlphaShort.setValue(testOperatorName)
+
+ assertThat(latest).isEqualTo(DERIVED_NAME)
+ }
+
+ @Test
+ fun isSingleCarrier_matchesParent() = runTest {
+ val latest by underTest.isSingleCarrier.collectLastValue()
+
+ // mobileIconsInteractor.isSingleCarrier.setValue(true)
+ isSingleCarrier.setValue(true)
+ assertThat(latest).isTrue()
+
+ // mobileIconsInteractor.isSingleCarrier.setValue(false)
+ isSingleCarrier.setValue(false)
+ assertThat(latest).isFalse()
+ }
+
+ @Test
+ fun isForceHidden_matchesParent() = runTest {
+ val latest by underTest.isForceHidden.collectLastValue()
+
+ // mobileIconsInteractor.isForceHidden.setValue(true)
+ isForceHidden.setValue(true)
+ assertThat(latest).isTrue()
+
+ // mobileIconsInteractor.isForceHidden.setValue(false)
+ isForceHidden.setValue(false)
+ assertThat(latest).isFalse()
+ }
+
+ @Test
+ fun isAllowedDuringAirplaneMode_matchesRepo() = runTest {
+ val latest by underTest.isAllowedDuringAirplaneMode.collectLastValue()
+
+ connectionRepo.isAllowedDuringAirplaneMode.setValue(true)
+ assertThat(latest).isTrue()
+
+ connectionRepo.isAllowedDuringAirplaneMode.setValue(false)
+ assertThat(latest).isFalse()
+ }
+
+ @Test
+ 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)
+
+ val latest by
+ underTest.signalLevelIcon.map { it as? SignalIconModel.Cellular }.collectLastValue()
+
+ assertThat(latest?.level).isEqualTo(1)
+
+ // TODO: need to provision MobileIconsInteractorKairos#isDefaultConnectionFailed +
+ // defaultSubscriptionHasDataEnabled?
+ assertThat(latest?.showExclamationMark).isEqualTo(false)
+ }
+
+ @Test
+ fun icon_usesLevelFromInteractor() = runTest {
+ connectionRepo.isNonTerrestrial.setValue(false)
+ connectionRepo.isInService.setValue(true)
+
+ val latest by underTest.signalLevelIcon.collectLastValue()
+
+ connectionRepo.primaryLevel.setValue(3)
+ assertThat(latest!!.level).isEqualTo(3)
+
+ connectionRepo.primaryLevel.setValue(1)
+ assertThat(latest!!.level).isEqualTo(1)
+ }
+
+ @Test
+ fun cellBasedIcon_usesNumberOfLevelsFromInteractor() = runTest {
+ connectionRepo.isNonTerrestrial.setValue(false)
+
+ val latest by
+ underTest.signalLevelIcon.map { it as? SignalIconModel.Cellular }.collectLastValue()
+
+ connectionRepo.numberOfLevels.setValue(5)
+ assertThat(latest!!.numberOfLevels).isEqualTo(5)
+
+ connectionRepo.numberOfLevels.setValue(2)
+ assertThat(latest!!.numberOfLevels).isEqualTo(2)
+ }
+
+ @Test
+ fun cellBasedIcon_defaultDataDisabled_showExclamationTrue() = runTest {
+ connectionRepo.isNonTerrestrial.setValue(false)
+ connectionRepo.dataEnabled.setValue(false)
+ defaultSubscriptionHasDataEnabled.setValue(false)
+
+ val latest by underTest.signalLevelIcon.collectLastValue()
+
+ assertThat((latest!! as SignalIconModel.Cellular).showExclamationMark).isTrue()
+ }
+
+ @Test
+ fun cellBasedIcon_defaultConnectionFailed_showExclamationTrue() = runTest {
+ connectionRepo.isNonTerrestrial.setValue(false)
+ // mobileIconsInteractor.isDefaultConnectionFailed.setValue(true)
+ isDefaultConnectionFailed.setValue(true)
+
+ val latest by underTest.signalLevelIcon.collectLastValue()
+
+ assertThat((latest!! as SignalIconModel.Cellular).showExclamationMark).isTrue()
+ }
+
+ @Test
+ fun cellBasedIcon_enabledAndNotFailed_showExclamationFalse() = runTest {
+ connectionRepo.isNonTerrestrial.setValue(false)
+ connectionRepo.isInService.setValue(true)
+ connectionRepo.dataEnabled.setValue(true)
+ // mobileIconsInteractor.isDefaultConnectionFailed.setValue(false)
+ isDefaultConnectionFailed.setValue(false)
+
+ val latest by underTest.signalLevelIcon.collectLastValue()
+
+ assertThat((latest!! as SignalIconModel.Cellular).showExclamationMark).isFalse()
+ }
+
+ @Test
+ fun cellBasedIcon_usesEmptyState_whenNotInService() = runTest {
+ val latest by
+ underTest.signalLevelIcon.map { it as SignalIconModel.Cellular }.collectLastValue()
+
+ connectionRepo.isNonTerrestrial.setValue(false)
+ connectionRepo.isInService.setValue(false)
+
+ 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
+ connectionRepo.isInService.setValue(true)
+ assertThat(latest?.level).isEqualTo(2)
+ assertThat(latest?.showExclamationMark).isFalse()
+ }
+
+ @Test
+ fun cellBasedIcon_usesCarrierNetworkState_whenInCarrierNetworkChangeMode() = runTest {
+ val latest by
+ underTest.signalLevelIcon.map { it as SignalIconModel.Cellular }.collectLastValue()
+
+ connectionRepo.isNonTerrestrial.setValue(false)
+ connectionRepo.isInService.setValue(true)
+ connectionRepo.carrierNetworkChangeActive.setValue(true)
+ connectionRepo.primaryLevel.setValue(1)
+ connectionRepo.cdmaLevel.setValue(1)
+
+ assertThat(latest!!.level).isEqualTo(1)
+ assertThat(latest!!.carrierNetworkChange).isTrue()
+
+ // SignalIconModel respects the current level
+ connectionRepo.primaryLevel.setValue(2)
+
+ assertThat(latest!!.level).isEqualTo(2)
+ assertThat(latest!!.carrierNetworkChange).isTrue()
+ }
+
+ @Test
+ fun satBasedIcon_isUsedWhenNonTerrestrial() = runTest {
+ val latest by underTest.signalLevelIcon.collectLastValue()
+
+ // Start off using cellular
+ assertThat(latest).isInstanceOf(SignalIconModel.Cellular::class.java)
+
+ connectionRepo.isNonTerrestrial.setValue(true)
+
+ 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() = runTest {
+ val latest by underTest.signalLevelIcon.collectLastValue()
+
+ // GIVEN a satellite connection
+ connectionRepo.isNonTerrestrial.setValue(true)
+ // GIVEN this carrier has set INFLATE_SIGNAL_STRENGTH
+ connectionRepo.inflateSignalStrength.setValue(true)
+
+ connectionRepo.primaryLevel.setValue(4)
+ assertThat(latest!!.level).isEqualTo(4)
+
+ connectionRepo.inflateSignalStrength.setValue(true)
+ connectionRepo.primaryLevel.setValue(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() = runTest {
+ val latest by underTest.signalLevelIcon.collectLastValue()
+
+ // GIVEN a satellite connection
+ connectionRepo.isNonTerrestrial.setValue(true)
+ // GIVEN this carrier has set INFLATE_SIGNAL_STRENGTH
+ connectionRepo.inflateSignalStrength.setValue(true)
+
+ connectionRepo.satelliteLevel.setValue(4)
+ assertThat(latest!!.level).isEqualTo(4)
+
+ connectionRepo.inflateSignalStrength.setValue(true)
+ connectionRepo.primaryLevel.setValue(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() = runTest {
+ val latest by underTest.signalLevelIcon.collectLastValue()
+
+ // GIVEN a satellite connection
+ connectionRepo.isNonTerrestrial.setValue(true)
+
+ // 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)
+ }
+
+ @EnableFlags(com.android.internal.telephony.flags.Flags.FLAG_CARRIER_ROAMING_NB_IOT_NTN)
+ @Test
+ fun satBasedIcon_usesSatelliteLevel_flagOn() = runTest {
+ val latest by underTest.signalLevelIcon.collectLastValue()
+
+ // GIVEN a satellite connection
+ connectionRepo.isNonTerrestrial.setValue(true)
+
+ // 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)
+ }
+
+ /**
+ * Context (b/377518113), this test will not be needed after FLAG_CARRIER_ROAMING_NB_IOT_NTN is
+ * rolled out. The new API should report 0 automatically if not in service.
+ */
+ @DisableFlags(com.android.internal.telephony.flags.Flags.FLAG_CARRIER_ROAMING_NB_IOT_NTN)
+ @Test
+ fun satBasedIcon_reportsLevelZeroWhenOutOfService() = runTest {
+ val latest by underTest.signalLevelIcon.collectLastValue()
+
+ // GIVEN a satellite connection
+ connectionRepo.isNonTerrestrial.setValue(true)
+ // GIVEN this carrier has set INFLATE_SIGNAL_STRENGTH
+ connectionRepo.inflateSignalStrength.setValue(true)
+
+ connectionRepo.primaryLevel.setValue(4)
+ assertThat(latest!!.level).isEqualTo(4)
+
+ connectionRepo.isInService.setValue(false)
+ connectionRepo.primaryLevel.setValue(4)
+
+ // THEN level reports 0, by policy
+ assertThat(latest!!.level).isEqualTo(0)
+ }
+
+ companion object {
+ private const val GSM_LEVEL = 1
+ private const val CDMA_LEVEL = 2
+
+ private const val SUB_1_ID = 1
+
+ private const val DEFAULT_NAME = "test default name"
+ private val DEFAULT_NAME_MODEL = NetworkNameModel.Default(DEFAULT_NAME)
+ private const val DERIVED_NAME = "test derived name"
+ private val DERIVED_NAME_MODEL = NetworkNameModel.IntentDerived(DERIVED_NAME)
+ }
+}
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
new file mode 100644
index 000000000000..4f6439d2d0b2
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconsInteractorKairosTest.kt
@@ -0,0 +1,965 @@
+/*
+ * 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.os.ParcelUuid
+import android.platform.test.annotations.EnableFlags
+import android.telephony.SubscriptionManager.INVALID_SUBSCRIPTION_ID
+import android.telephony.SubscriptionManager.PROFILE_CLASS_PROVISIONING
+import android.telephony.SubscriptionManager.PROFILE_CLASS_UNSET
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.settingslib.mobile.MobileMappings
+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.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.fake
+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.testKosmos
+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.whenever
+
+@OptIn(ExperimentalKairosApi::class)
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class MobileIconsInteractorKairosTest : SysuiTestCase() {
+
+ private val kosmos =
+ testKosmos().apply {
+ 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)
+ }
+
+ private val Kosmos.underTest
+ get() = mobileIconsInteractorKairos
+
+ private fun runTest(block: suspend KairosTestScope.() -> Unit) =
+ kosmos.run { runKairosTest { block() } }
+
+ @Test
+ fun filteredSubscriptions_default() = runTest {
+ mobileConnectionsRepositoryKairos.fake.subscriptions.setValue(emptyList())
+ val latest by underTest.filteredSubscriptions.collectLastValue()
+
+ 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() = runTest {
+ mobileConnectionsRepositoryKairos.fake.subscriptions.setValue(
+ listOf(SUB_1, SUB_3_OPP, SUB_4_OPP)
+ )
+ mobileConnectionsRepositoryKairos.fake.setActiveMobileDataSubscriptionId(SUB_4_ID)
+
+ val latest by underTest.filteredSubscriptions.collectLastValue()
+
+ assertThat(latest).isEqualTo(listOf(SUB_1, SUB_3_OPP, SUB_4_OPP))
+ }
+
+ @Test
+ fun filteredSubscriptions_nonOpportunistic_updatesWithMultipleSubs() = runTest {
+ mobileConnectionsRepositoryKairos.fake.subscriptions.setValue(listOf(SUB_1, SUB_2))
+
+ val latest by underTest.filteredSubscriptions.collectLastValue()
+
+ assertThat(latest).isEqualTo(listOf(SUB_1, SUB_2))
+ }
+
+ @Test
+ 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 underTest.filteredSubscriptions.collectLastValue()
+
+ assertThat(latest).isEqualTo(listOf(SUB_3_OPP, SUB_4_OPP))
+ }
+
+ @Test
+ 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 underTest.filteredSubscriptions.collectLastValue()
+
+ assertThat(latest).isEqualTo(listOf(sub1, sub2))
+ }
+
+ @Test
+ 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 underTest.filteredSubscriptions.collectLastValue()
+
+ // 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() = 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 underTest.filteredSubscriptions.collectLastValue()
+
+ // 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() =
+ runTest {
+ val (sub1, sub3) =
+ createSubscriptionPair(
+ subscriptionIds = Pair(SUB_1_ID, SUB_3_ID),
+ opportunistic = Pair(false, true),
+ grouped = true,
+ )
+ mobileConnectionsRepositoryKairos.fake.subscriptions.setValue(listOf(sub1, sub3))
+ mobileConnectionsRepositoryKairos.fake.setActiveMobileDataSubscriptionId(SUB_1_ID)
+ whenever(carrierConfigTracker.alwaysShowPrimarySignalBarInOpportunisticNetworkDefault)
+ .thenReturn(true)
+
+ val latest by underTest.filteredSubscriptions.collectLastValue()
+
+ // Filtered subscriptions should show the primary (non-opportunistic) if the config is
+ // true
+ assertThat(latest).isEqualTo(listOf(sub1))
+ }
+
+ @Test
+ fun filteredSubscriptions_oneOpportunistic_grouped_configTrue_showsPrimary_nonActive_1() =
+ runTest {
+ val (sub1, sub3) =
+ createSubscriptionPair(
+ subscriptionIds = Pair(SUB_1_ID, SUB_3_ID),
+ opportunistic = Pair(false, true),
+ grouped = true,
+ )
+ mobileConnectionsRepositoryKairos.fake.subscriptions.setValue(listOf(sub1, sub3))
+ mobileConnectionsRepositoryKairos.fake.setActiveMobileDataSubscriptionId(SUB_3_ID)
+ whenever(carrierConfigTracker.alwaysShowPrimarySignalBarInOpportunisticNetworkDefault)
+ .thenReturn(true)
+
+ val latest by underTest.filteredSubscriptions.collectLastValue()
+
+ // Filtered subscriptions should show the primary (non-opportunistic) if the config is
+ // true
+ assertThat(latest).isEqualTo(listOf(sub1))
+ }
+
+ @Test
+ 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 underTest.filteredSubscriptions.collectLastValue()
+
+ assertThat(latest).isEqualTo(listOf(sub3))
+ }
+
+ @Test
+ 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 underTest.filteredSubscriptions.collectLastValue()
+
+ assertThat(latest).isEqualTo(listOf(sub1))
+ }
+
+ @Test
+ 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,
+ )
+
+ mobileConnectionsRepositoryKairos.fake.subscriptions.setValue(listOf(sub1))
+
+ // WHEN filtering is applied
+ val latest by underTest.filteredSubscriptions.collectLastValue()
+
+ // THEN the provisioning sub is still present (unfiltered)
+ assertThat(latest).isEqualTo(listOf(sub1))
+ }
+
+ @Test
+ 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,
+ )
+
+ mobileConnectionsRepositoryKairos.fake.subscriptions.setValue(listOf(sub1, sub2))
+
+ val latest by underTest.filteredSubscriptions.collectLastValue()
+
+ 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() = 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,
+ )
+
+ val sub2 =
+ SubscriptionModel(
+ subscriptionId = 2,
+ isOpportunistic = true,
+ groupUuid = groupUuid,
+ carrierName = "Carrier 2",
+ profileClass = PROFILE_CLASS_UNSET,
+ )
+
+ // GIVEN active subId is 1
+ mobileConnectionsRepositoryKairos.fake.subscriptions.setValue(listOf(sub1, sub2))
+ mobileConnectionsRepositoryKairos.fake.setActiveMobileDataSubscriptionId(1)
+
+ // THEN filtering of provisioning subs takes place first, and we result in sub2
+
+ val latest by underTest.filteredSubscriptions.collectLastValue()
+
+ assertThat(latest).isEqualTo(listOf(sub2))
+ }
+
+ @Test
+ fun filteredSubscriptions_groupedPairAndNonProvisioned_groupedFilteringStillHappens() =
+ 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
+ val (sub1, sub3) =
+ createSubscriptionPair(
+ subscriptionIds = Pair(SUB_1_ID, SUB_3_ID),
+ opportunistic = Pair(true, true),
+ grouped = true,
+ )
+
+ val sub2 =
+ SubscriptionModel(
+ subscriptionId = 2,
+ isOpportunistic = true,
+ groupUuid = null,
+ carrierName = "Carrier 2",
+ profileClass = PROFILE_CLASS_PROVISIONING,
+ )
+
+ mobileConnectionsRepositoryKairos.fake.subscriptions.setValue(listOf(sub1, sub2, sub3))
+ mobileConnectionsRepositoryKairos.fake.setActiveMobileDataSubscriptionId(1)
+
+ val latest by underTest.filteredSubscriptions.collectLastValue()
+
+ assertThat(latest).isEqualTo(listOf(sub1))
+ }
+
+ @Test
+ fun filteredSubscriptions_subNotExclusivelyNonTerrestrial_hasSub() = runTest {
+ val notExclusivelyNonTerrestrialSub =
+ SubscriptionModel(
+ isExclusivelyNonTerrestrial = false,
+ subscriptionId = 5,
+ carrierName = "Carrier 5",
+ profileClass = PROFILE_CLASS_UNSET,
+ )
+
+ mobileConnectionsRepositoryKairos.fake.subscriptions.setValue(
+ listOf(notExclusivelyNonTerrestrialSub)
+ )
+
+ val latest by underTest.filteredSubscriptions.collectLastValue()
+
+ assertThat(latest).isEqualTo(listOf(notExclusivelyNonTerrestrialSub))
+ }
+
+ @Test
+ fun filteredSubscriptions_subExclusivelyNonTerrestrial_doesNotHaveSub() = runTest {
+ val exclusivelyNonTerrestrialSub =
+ SubscriptionModel(
+ isExclusivelyNonTerrestrial = true,
+ subscriptionId = 5,
+ carrierName = "Carrier 5",
+ profileClass = PROFILE_CLASS_UNSET,
+ )
+
+ mobileConnectionsRepositoryKairos.fake.subscriptions.setValue(
+ listOf(exclusivelyNonTerrestrialSub)
+ )
+
+ val latest by underTest.filteredSubscriptions.collectLastValue()
+
+ assertThat(latest).isEmpty()
+ }
+
+ @Test
+ 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,
+ )
+
+ mobileConnectionsRepositoryKairos.fake.subscriptions.setValue(
+ listOf(otherSub1, exclusivelyNonTerrestrialSub, otherSub2)
+ )
+
+ val latest by underTest.filteredSubscriptions.collectLastValue()
+
+ assertThat(latest).isEqualTo(listOf(otherSub1, otherSub2))
+ }
+
+ @Test
+ fun filteredSubscriptions_exclusivelyNonTerrestrialSub_andOpportunistic_bothFiltersHappen() =
+ runTest {
+ // Exclusively non-terrestrial sub
+ val exclusivelyNonTerrestrialSub =
+ SubscriptionModel(
+ isExclusivelyNonTerrestrial = true,
+ subscriptionId = 5,
+ carrierName = "Carrier 5",
+ profileClass = PROFILE_CLASS_UNSET,
+ )
+
+ // Opportunistic subs
+ val (sub3, sub4) =
+ createSubscriptionPair(
+ subscriptionIds = Pair(SUB_3_ID, SUB_4_ID),
+ opportunistic = Pair(true, true),
+ grouped = true,
+ )
+
+ // WHEN both an exclusively non-terrestrial sub and opportunistic sub pair is included
+ mobileConnectionsRepositoryKairos.fake.subscriptions.setValue(
+ listOf(sub3, sub4, exclusivelyNonTerrestrialSub)
+ )
+ mobileConnectionsRepositoryKairos.fake.setActiveMobileDataSubscriptionId(SUB_3_ID)
+
+ val latest by underTest.filteredSubscriptions.collectLastValue()
+
+ // THEN both the only-non-terrestrial sub and the non-active sub are filtered out,
+ // leaving only sub3.
+ assertThat(latest).isEqualTo(listOf(sub3))
+ }
+
+ @Test
+ fun activeDataConnection_turnedOn() = runTest {
+ val connection1 =
+ mobileConnectionsRepositoryKairos.fake.mobileConnectionsBySubId.sample()[SUB_1_ID]!!
+
+ connection1.dataEnabled.setValue(true)
+
+ val latest by underTest.activeDataConnectionHasDataEnabled.collectLastValue()
+
+ assertThat(latest).isTrue()
+ }
+
+ @Test
+ fun activeDataConnection_turnedOff() = runTest {
+ val connection1 =
+ mobileConnectionsRepositoryKairos.fake.mobileConnectionsBySubId.sample()[SUB_1_ID]!!
+
+ connection1.dataEnabled.setValue(true)
+ val latest by underTest.activeDataConnectionHasDataEnabled.collectLastValue()
+
+ connection1.dataEnabled.setValue(false)
+
+ assertThat(latest).isFalse()
+ }
+
+ @Test
+ fun activeDataConnection_invalidSubId() = runTest {
+ val latest by underTest.activeDataConnectionHasDataEnabled.collectLastValue()
+
+ mobileConnectionsRepositoryKairos.fake.setActiveMobileDataSubscriptionId(
+ INVALID_SUBSCRIPTION_ID
+ )
+
+ // An invalid active subId should tell us that data is off
+ assertThat(latest).isFalse()
+ }
+
+ @Test
+ fun failedConnection_default_validated_notFailed() = runTest {
+ val latest by underTest.isDefaultConnectionFailed.collectLastValue()
+
+ mobileConnectionsRepositoryKairos.fake.mobileIsDefault.setValue(true)
+ mobileConnectionsRepositoryKairos.fake.defaultConnectionIsValidated.setValue(true)
+
+ assertThat(latest).isFalse()
+ }
+
+ @Test
+ fun failedConnection_notDefault_notValidated_notFailed() = runTest {
+ val latest by underTest.isDefaultConnectionFailed.collectLastValue()
+
+ mobileConnectionsRepositoryKairos.fake.mobileIsDefault.setValue(false)
+ mobileConnectionsRepositoryKairos.fake.defaultConnectionIsValidated.setValue(false)
+
+ assertThat(latest).isFalse()
+ }
+
+ @Test
+ fun failedConnection_default_notValidated_failed() = runTest {
+ val latest by underTest.isDefaultConnectionFailed.collectLastValue()
+
+ mobileConnectionsRepositoryKairos.fake.mobileIsDefault.setValue(true)
+ mobileConnectionsRepositoryKairos.fake.defaultConnectionIsValidated.setValue(false)
+
+ assertThat(latest).isTrue()
+ }
+
+ @Test
+ fun failedConnection_carrierMergedDefault_notValidated_failed() = runTest {
+ val latest by underTest.isDefaultConnectionFailed.collectLastValue()
+
+ mobileConnectionsRepositoryKairos.fake.hasCarrierMergedConnection.setValue(true)
+ mobileConnectionsRepositoryKairos.fake.defaultConnectionIsValidated.setValue(false)
+
+ assertThat(latest).isTrue()
+ }
+
+ /** Regression test for b/275076959. */
+ @Test
+ fun failedConnection_dataSwitchInSameGroup_notFailed() = runTest {
+ val latest by underTest.isDefaultConnectionFailed.collectLastValue()
+
+ mobileConnectionsRepositoryKairos.fake.mobileIsDefault.setValue(true)
+ mobileConnectionsRepositoryKairos.fake.defaultConnectionIsValidated.setValue(true)
+ 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()
+ }
+
+ @Test
+ fun failedConnection_dataSwitchNotInSameGroup_isFailed() = runTest {
+ val latest by underTest.isDefaultConnectionFailed.collectLastValue()
+
+ mobileConnectionsRepositoryKairos.fake.mobileIsDefault.setValue(true)
+ mobileConnectionsRepositoryKairos.fake.defaultConnectionIsValidated.setValue(true)
+ runCurrent()
+
+ // WHEN the connection is invalidated without a activeSubChangedInGroupEvent
+ mobileConnectionsRepositoryKairos.fake.defaultConnectionIsValidated.setValue(false)
+
+ // THEN the connection is immediately marked as failed
+ assertThat(latest).isTrue()
+ }
+
+ @Test
+ fun alwaysShowDataRatIcon_configHasTrue() = runTest {
+ val latest by underTest.alwaysShowDataRatIcon.collectLastValue()
+
+ val config = MobileMappings.Config()
+ config.alwaysShowDataRatIcon = true
+ mobileConnectionsRepositoryKairos.fake.defaultDataSubRatConfig.setValue(config)
+
+ assertThat(latest).isTrue()
+ }
+
+ @Test
+ fun alwaysShowDataRatIcon_configHasFalse() = runTest {
+ val latest by underTest.alwaysShowDataRatIcon.collectLastValue()
+
+ val config = MobileMappings.Config()
+ config.alwaysShowDataRatIcon = false
+ mobileConnectionsRepositoryKairos.fake.defaultDataSubRatConfig.setValue(config)
+
+ assertThat(latest).isFalse()
+ }
+
+ @Test
+ fun alwaysUseCdmaLevel_configHasTrue() = runTest {
+ val latest by underTest.alwaysUseCdmaLevel.collectLastValue()
+
+ val config = MobileMappings.Config()
+ config.alwaysShowCdmaRssi = true
+ mobileConnectionsRepositoryKairos.fake.defaultDataSubRatConfig.setValue(config)
+
+ assertThat(latest).isTrue()
+ }
+
+ @Test
+ fun alwaysUseCdmaLevel_configHasFalse() = runTest {
+ val latest by underTest.alwaysUseCdmaLevel.collectLastValue()
+
+ val config = MobileMappings.Config()
+ config.alwaysShowCdmaRssi = false
+ mobileConnectionsRepositoryKairos.fake.defaultDataSubRatConfig.setValue(config)
+
+ assertThat(latest).isFalse()
+ }
+
+ @Test
+ fun isSingleCarrier_zeroSubscriptions_false() = runTest {
+ val latest by underTest.isSingleCarrier.collectLastValue()
+
+ mobileConnectionsRepositoryKairos.fake.subscriptions.setValue(emptyList())
+
+ assertThat(latest).isFalse()
+ }
+
+ @Test
+ fun isSingleCarrier_oneSubscription_true() = runTest {
+ val latest by underTest.isSingleCarrier.collectLastValue()
+
+ mobileConnectionsRepositoryKairos.fake.subscriptions.setValue(listOf(SUB_1))
+
+ assertThat(latest).isTrue()
+ }
+
+ @Test
+ fun isSingleCarrier_twoSubscriptions_false() = runTest {
+ val latest by underTest.isSingleCarrier.collectLastValue()
+
+ mobileConnectionsRepositoryKairos.fake.subscriptions.setValue(listOf(SUB_1, SUB_2))
+
+ assertThat(latest).isFalse()
+ }
+
+ @Test
+ fun isSingleCarrier_updates() = runTest {
+ val latest by underTest.isSingleCarrier.collectLastValue()
+
+ mobileConnectionsRepositoryKairos.fake.subscriptions.setValue(listOf(SUB_1))
+ assertThat(latest).isTrue()
+
+ mobileConnectionsRepositoryKairos.fake.subscriptions.setValue(listOf(SUB_1, SUB_2))
+ assertThat(latest).isFalse()
+ }
+
+ @Test
+ fun mobileIsDefault_mobileFalseAndCarrierMergedFalse_false() = runTest {
+ val latest by underTest.mobileIsDefault.collectLastValue()
+
+ mobileConnectionsRepositoryKairos.fake.mobileIsDefault.setValue(false)
+ mobileConnectionsRepositoryKairos.fake.hasCarrierMergedConnection.setValue(false)
+
+ assertThat(latest).isFalse()
+ }
+
+ @Test
+ fun mobileIsDefault_mobileTrueAndCarrierMergedFalse_true() = runTest {
+ val latest by underTest.mobileIsDefault.collectLastValue()
+
+ mobileConnectionsRepositoryKairos.fake.mobileIsDefault.setValue(true)
+ mobileConnectionsRepositoryKairos.fake.hasCarrierMergedConnection.setValue(false)
+
+ assertThat(latest).isTrue()
+ }
+
+ /** Regression test for b/272586234. */
+ @Test
+ fun mobileIsDefault_mobileFalseAndCarrierMergedTrue_true() = runTest {
+ val latest by underTest.mobileIsDefault.collectLastValue()
+
+ mobileConnectionsRepositoryKairos.fake.mobileIsDefault.setValue(false)
+ mobileConnectionsRepositoryKairos.fake.hasCarrierMergedConnection.setValue(true)
+
+ assertThat(latest).isTrue()
+ }
+
+ @Test
+ fun mobileIsDefault_updatesWhenRepoUpdates() = runTest {
+ val latest by underTest.mobileIsDefault.collectLastValue()
+
+ mobileConnectionsRepositoryKairos.fake.mobileIsDefault.setValue(true)
+ assertThat(latest).isTrue()
+
+ mobileConnectionsRepositoryKairos.fake.mobileIsDefault.setValue(false)
+ assertThat(latest).isFalse()
+
+ 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() = runTest {
+ val latest by underTest.isDefaultConnectionFailed.collectLastValue()
+
+ mobileConnectionsRepositoryKairos.fake.mobileIsDefault.setValue(true)
+ mobileConnectionsRepositoryKairos.fake.defaultConnectionIsValidated.setValue(true)
+ 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()
+
+ // 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()
+ }
+
+ @Test
+ fun dataSwitch_inSameGroup_notValidated_immediatelyMarkedAsFailed() = runTest {
+ val latest by underTest.isDefaultConnectionFailed.collectLastValue()
+
+ mobileConnectionsRepositoryKairos.fake.mobileIsDefault.setValue(true)
+ mobileConnectionsRepositoryKairos.fake.defaultConnectionIsValidated.setValue(false)
+ runCurrent()
+
+ mobileConnectionsRepositoryKairos.fake.activeSubChangedInGroupEvent.emit(Unit)
+
+ assertThat(latest).isTrue()
+ }
+
+ @Test
+ fun dataSwitch_loseValidation_thenSwitchHappens_clearsForcedBit() = runTest {
+ val latest by underTest.isDefaultConnectionFailed.collectLastValue()
+
+ // GIVEN the network starts validated
+ mobileConnectionsRepositoryKairos.fake.mobileIsDefault.setValue(true)
+ mobileConnectionsRepositoryKairos.fake.defaultConnectionIsValidated.setValue(true)
+ runCurrent()
+
+ // WHEN a data change happens in the same group
+ mobileConnectionsRepositoryKairos.fake.activeSubChangedInGroupEvent.emit(Unit)
+
+ // WHEN the validation bit is lost
+ mobileConnectionsRepositoryKairos.fake.defaultConnectionIsValidated.setValue(false)
+ runCurrent()
+
+ // WHEN another data change happens in the same group
+ mobileConnectionsRepositoryKairos.fake.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()
+ }
+
+ @Test
+ fun dataSwitch_whileAlreadyForcingValidation_resetsClock() = runTest {
+ val latest by underTest.isDefaultConnectionFailed.collectLastValue()
+ mobileConnectionsRepositoryKairos.fake.mobileIsDefault.setValue(true)
+ mobileConnectionsRepositoryKairos.fake.defaultConnectionIsValidated.setValue(true)
+ runCurrent()
+
+ mobileConnectionsRepositoryKairos.fake.activeSubChangedInGroupEvent.emit(Unit)
+
+ testScope.advanceTimeBy(1000)
+
+ // WHEN another change in same group event happens
+ mobileConnectionsRepositoryKairos.fake.activeSubChangedInGroupEvent.emit(Unit)
+ mobileConnectionsRepositoryKairos.fake.defaultConnectionIsValidated.setValue(false)
+ runCurrent()
+
+ // THEN the forced validation remains for exactly 2 more seconds from now
+
+ // 1.500s from second event
+ testScope.advanceTimeBy(1500)
+ assertThat(latest).isFalse()
+
+ // 2.001s from the second event
+ testScope.advanceTimeBy(501)
+ assertThat(latest).isTrue()
+ }
+
+ @Test
+ fun isForceHidden_repoHasMobileHidden_true() = runTest {
+ val latest by underTest.isForceHidden.collectLastValue()
+
+ kosmos.connectivityRepository.fake.setForceHiddenIcons(setOf(ConnectivitySlot.MOBILE))
+
+ assertThat(latest).isTrue()
+ }
+
+ @Test
+ fun isForceHidden_repoDoesNotHaveMobileHidden_false() = runTest {
+ val latest by underTest.isForceHidden.collectLastValue()
+
+ kosmos.connectivityRepository.fake.setForceHiddenIcons(setOf(ConnectivitySlot.WIFI))
+
+ assertThat(latest).isFalse()
+ }
+
+ @Test
+ fun deviceBasedEmergencyMode_emergencyCallsOnly_followsDeviceServiceStateFromRepo() = runTest {
+ val latest by underTest.isDeviceInEmergencyCallsOnlyMode.collectLastValue()
+
+ mobileConnectionsRepositoryKairos.fake.isDeviceEmergencyCallCapable.setValue(true)
+
+ assertThat(latest).isTrue()
+
+ mobileConnectionsRepositoryKairos.fake.isDeviceEmergencyCallCapable.setValue(false)
+
+ assertThat(latest).isFalse()
+ }
+
+ @Test
+ @EnableFlags(NewStatusBarIcons.FLAG_NAME, StatusBarRootModernization.FLAG_NAME)
+ fun isStackable_tracksNumberOfSubscriptions() = runTest {
+ val latest by underTest.isStackable.collectLastValue()
+
+ mobileConnectionsRepositoryKairos.fake.subscriptions.setValue(listOf(SUB_1))
+ assertThat(latest).isFalse()
+
+ mobileConnectionsRepositoryKairos.fake.subscriptions.setValue(listOf(SUB_1, SUB_2))
+ assertThat(latest).isTrue()
+
+ 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() = runTest {
+ val latest by underTest.isStackable.collectLastValue()
+
+ mobileConnectionsRepositoryKairos.fake.subscriptions.setValue(listOf(SUB_1, SUB_2))
+ setNumberOfLevelsForSubId(SUB_1_ID, 5)
+ setNumberOfLevelsForSubId(SUB_2_ID, 5)
+ assertThat(latest).isTrue()
+
+ mobileConnectionsRepositoryKairos.fake.mobileConnectionsBySubId
+ .sample()[SUB_1_ID]!!
+ .isNonTerrestrial
+ .setValue(true)
+
+ assertThat(latest).isFalse()
+ }
+
+ @Test
+ @EnableFlags(NewStatusBarIcons.FLAG_NAME, StatusBarRootModernization.FLAG_NAME)
+ fun isStackable_checksForNumberOfBars() = runTest {
+ val latest by underTest.isStackable.collectLastValue()
+
+ // 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()
+
+ // Change the number of levels to be different than SUB_2
+ setNumberOfLevelsForSubId(SUB_1_ID, 6)
+
+ assertThat(latest).isFalse()
+ }
+
+ private suspend fun KairosTestScope.setNumberOfLevelsForSubId(subId: Int, numberOfLevels: Int) {
+ mobileConnectionsRepositoryKairos.fake.mobileConnectionsBySubId
+ .sample()[subId]!!
+ .numberOfLevels
+ .setValue(numberOfLevels)
+ }
+
+ /**
+ * Convenience method for creating a pair of subscriptions to test the filteredSubscriptions
+ * flow.
+ */
+ private fun createSubscriptionPair(
+ subscriptionIds: Pair<Int, Int>,
+ opportunistic: Pair<Boolean, Boolean> = Pair(false, false),
+ grouped: Boolean = false,
+ ): Pair<SubscriptionModel, SubscriptionModel> {
+ val groupUuid = if (grouped) ParcelUuid(UUID.randomUUID()) else null
+ val sub1 =
+ SubscriptionModel(
+ subscriptionId = subscriptionIds.first,
+ isOpportunistic = opportunistic.first,
+ groupUuid = groupUuid,
+ carrierName = "Carrier ${subscriptionIds.first}",
+ profileClass = PROFILE_CLASS_UNSET,
+ )
+
+ val sub2 =
+ SubscriptionModel(
+ subscriptionId = subscriptionIds.second,
+ isOpportunistic = opportunistic.second,
+ groupUuid = groupUuid,
+ carrierName = "Carrier ${opportunistic.second}",
+ profileClass = PROFILE_CLASS_UNSET,
+ )
+
+ return Pair(sub1, sub2)
+ }
+
+ companion object {
+
+ private const val SUB_1_ID = 1
+ private val SUB_1 =
+ SubscriptionModel(
+ subscriptionId = SUB_1_ID,
+ carrierName = "Carrier $SUB_1_ID",
+ profileClass = PROFILE_CLASS_UNSET,
+ )
+
+ private const val SUB_2_ID = 2
+ private val SUB_2 =
+ SubscriptionModel(
+ subscriptionId = SUB_2_ID,
+ carrierName = "Carrier $SUB_2_ID",
+ profileClass = PROFILE_CLASS_UNSET,
+ )
+
+ private const val SUB_3_ID = 3
+ private val SUB_3_OPP =
+ SubscriptionModel(
+ subscriptionId = SUB_3_ID,
+ isOpportunistic = true,
+ groupUuid = ParcelUuid(UUID.randomUUID()),
+ carrierName = "Carrier $SUB_3_ID",
+ profileClass = PROFILE_CLASS_UNSET,
+ )
+
+ private const val SUB_4_ID = 4
+ private val SUB_4_OPP =
+ SubscriptionModel(
+ subscriptionId = SUB_4_ID,
+ isOpportunistic = true,
+ groupUuid = ParcelUuid(UUID.randomUUID()),
+ carrierName = "Carrier $SUB_4_ID",
+ profileClass = PROFILE_CLASS_UNSET,
+ )
+ }
+}
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..91b3896332f5 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,10 @@ class FakeHomeStatusBarViewModel(
override val primaryOngoingActivityChip: MutableStateFlow<OngoingActivityChipModel> =
MutableStateFlow(OngoingActivityChipModel.Inactive())
- override val ongoingActivityChips = MutableStateFlow(MultipleOngoingActivityChipsModel())
+ override val ongoingActivityChips =
+ MutableStateFlow(
+ ChipsVisibilityModel(MultipleOngoingActivityChipsModel(), areChipsAllowed = false)
+ )
override val ongoingActivityChipsLegacy =
MutableStateFlow(MultipleOngoingActivityChipsModelLegacy())
@@ -62,7 +66,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..12cf3b6cd2cf 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
@@ -57,6 +58,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 +66,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 +91,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 +99,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
@@ -500,9 +503,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 +515,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 +527,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 +539,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 +550,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 +561,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 +572,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,14 +585,104 @@ 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.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 latest by collectLastValue(underTest.isHomeStatusBarAllowedByScene)
+ 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 +816,8 @@ 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)
@@ -725,7 +826,7 @@ class HomeStatusBarViewModelImplTest : SysuiTestCase() {
headsUpNotificationRepository.setNotifications(
UnconfinedFakeHeadsUpRowRepository(
key = "key",
- pinnedStatus = MutableStateFlow(PinnedStatus.PinnedByUser),
+ pinnedStatus = MutableStateFlow(PinnedStatus.PinnedBySystem),
)
)
@@ -733,6 +834,194 @@ class HomeStatusBarViewModelImplTest : SysuiTestCase() {
}
@Test
+ @DisableFlags(StatusBarNoHunBehavior.FLAG_NAME)
+ fun canShowOngoingActivityChips_statusBarNotHidden_noSecureCamera_hunByUser_noHunFlagOff_true() =
+ kosmos.runTest {
+ val latest by collectLastValue(underTest.canShowOngoingActivityChips)
+
+ transitionKeyguardToGone()
+
+ headsUpNotificationRepository.setNotifications(
+ UnconfinedFakeHeadsUpRowRepository(
+ key = "key",
+ pinnedStatus = MutableStateFlow(PinnedStatus.PinnedByUser),
+ )
+ )
+
+ 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 {
+ val latest by collectLastValue(underTest.ongoingActivityChips)
+
+ // home status bar not allowed
+ kosmos.sceneContainerRepository.snapToScene(Scenes.Lockscreen)
+ kosmos.keyguardOcclusionRepository.setShowWhenLockedActivityInfo(false, taskInfo = null)
+
+ assertThat(latest!!.areChipsAllowed).isFalse()
+ }
+
+ @Test
+ @EnableChipsModernization
+ fun ongoingActivityChips_statusBarNotHidden_noSecureCamera_noHun_isAllowed() =
+ kosmos.runTest {
+ val latest by collectLastValue(underTest.ongoingActivityChips)
+
+ transitionKeyguardToGone()
+
+ assertThat(latest!!.areChipsAllowed).isTrue()
+ }
+
+ @Test
+ @EnableChipsModernization
+ fun ongoingActivityChips_statusBarNotHidden_secureCamera_noHun_notAllowed() =
+ kosmos.runTest {
+ val latest by collectLastValue(underTest.ongoingActivityChips)
+
+ fakeKeyguardTransitionRepository.sendTransitionSteps(
+ from = KeyguardState.LOCKSCREEN,
+ to = KeyguardState.OCCLUDED,
+ testScope = testScope,
+ )
+ kosmos.keyguardInteractor.onCameraLaunchDetected(CAMERA_LAUNCH_SOURCE_POWER_DOUBLE_TAP)
+
+ assertThat(latest!!.areChipsAllowed).isFalse()
+ }
+
+ @Test
+ @DisableFlags(StatusBarNoHunBehavior.FLAG_NAME)
+ @EnableChipsModernization
+ fun ongoingActivityChips_statusBarNotHidden_noSecureCamera_hunBySystem_noHunFlagOff_notAllowed() =
+ kosmos.runTest {
+ val latest by collectLastValue(underTest.ongoingActivityChips)
+
+ transitionKeyguardToGone()
+
+ headsUpNotificationRepository.setNotifications(
+ UnconfinedFakeHeadsUpRowRepository(
+ key = "key",
+ pinnedStatus = MutableStateFlow(PinnedStatus.PinnedBySystem),
+ )
+ )
+
+ assertThat(latest!!.areChipsAllowed).isFalse()
+ }
+
+ @Test
+ @DisableFlags(StatusBarNoHunBehavior.FLAG_NAME)
+ @EnableChipsModernization
+ fun ongoingActivityChips_statusBarNotHidden_noSecureCamera_hunByUser_noHunFlagOff_isAllowed() =
+ kosmos.runTest {
+ val latest by collectLastValue(underTest.ongoingActivityChips)
+
+ transitionKeyguardToGone()
+
+ headsUpNotificationRepository.setNotifications(
+ UnconfinedFakeHeadsUpRowRepository(
+ key = "key",
+ pinnedStatus = MutableStateFlow(PinnedStatus.PinnedByUser),
+ )
+ )
+
+ assertThat(latest!!.areChipsAllowed).isTrue()
+ }
+
+ @Test
+ @EnableFlags(StatusBarNoHunBehavior.FLAG_NAME)
+ @EnableChipsModernization
+ fun ongoingActivityChips_tatusBarNotHidden_noSecureCamera_hunBySystem_noHunFlagOn_isAllowed() =
+ kosmos.runTest {
+ val latest by collectLastValue(underTest.ongoingActivityChips)
+
+ transitionKeyguardToGone()
+
+ headsUpNotificationRepository.setNotifications(
+ UnconfinedFakeHeadsUpRowRepository(
+ key = "key",
+ pinnedStatus = MutableStateFlow(PinnedStatus.PinnedBySystem),
+ )
+ )
+
+ assertThat(latest!!.areChipsAllowed).isTrue()
+ }
+
+ @Test
+ @EnableFlags(StatusBarNoHunBehavior.FLAG_NAME)
+ @EnableChipsModernization
+ fun ongoingActivityChips_statusBarNotHidden_noSecureCamera_hunByUser_noHunFlagOn_isAllowed() =
+ kosmos.runTest {
+ val latest by collectLastValue(underTest.ongoingActivityChips)
+
+ transitionKeyguardToGone()
+
+ headsUpNotificationRepository.setNotifications(
+ UnconfinedFakeHeadsUpRowRepository(
+ key = "key",
+ pinnedStatus = MutableStateFlow(PinnedStatus.PinnedByUser),
+ )
+ )
+
+ assertThat(latest!!.areChipsAllowed).isTrue()
+ }
+
+ @Test
+ @EnableFlags(StatusBarNotifChips.FLAG_NAME)
+ @EnableChipsModernization
+ fun ongoingActivityChips_followsChipsViewModel() =
+ kosmos.runTest {
+ val latest by collectLastValue(underTest.ongoingActivityChips)
+ transitionKeyguardToGone()
+
+ screenRecordRepository.screenRecordState.value = ScreenRecordModel.Recording
+
+ assertIsScreenRecordChip(latest!!.chips.active[0])
+
+ addOngoingCallState(key = "call")
+
+ assertIsScreenRecordChip(latest!!.chips.active[0])
+ assertIsCallChip(latest!!.chips.active[1], "call", context)
+ }
+
+ @Test
fun isClockVisible_allowedByDisableFlags_visible() =
kosmos.runTest {
val latest by collectLastValue(underTest.isClockVisible)
@@ -892,7 +1181,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 +1198,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 +1218,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 +1232,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 {
@@ -1289,4 +1658,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/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/plugin/src/com/android/systemui/plugins/clocks/ClockController.kt b/packages/SystemUI/plugin/src/com/android/systemui/plugins/clocks/ClockController.kt
index 32fec3277f18..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
@@ -37,8 +37,17 @@ interface ClockController {
val events: ClockEvents
/** Initializes various rendering parameters. If never called, provides reasonable defaults. */
- fun initialize(isDarkTheme: Boolean, dozeFraction: Float, foldFraction: Float)
+ fun initialize(
+ isDarkTheme: Boolean,
+ dozeFraction: Float,
+ foldFraction: Float,
+ 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/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..f9ff75d5fdc8 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(long1.toULong())})" }) {
bool1 = changed
- int1 = left
- int2 = top
- long1 = right.toLong()
- long2 = bottom.toLong()
+ long1 = VRect(left, top, right, bottom).data.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(long1.toULong())})" }) { long1 = VPointF(x, y).data.toLong() }
}
companion object {
diff --git a/packages/SystemUI/plugin/src/com/android/systemui/plugins/clocks/ClockProviderPlugin.kt b/packages/SystemUI/plugin/src/com/android/systemui/plugins/clocks/ClockProviderPlugin.kt
index 7426f061b84c..0ef62a32a9c4 100644
--- a/packages/SystemUI/plugin/src/com/android/systemui/plugins/clocks/ClockProviderPlugin.kt
+++ b/packages/SystemUI/plugin/src/com/android/systemui/plugins/clocks/ClockProviderPlugin.kt
@@ -53,5 +53,21 @@ interface ClockProvider {
/** Identifies a clock design */
typealias ClockId = String
-/** Some data about a clock design */
-data class ClockMetadata(val clockId: ClockId)
+/** Some metadata about a clock design */
+data class ClockMetadata(
+ /** Id for the clock design. */
+ val clockId: ClockId,
+
+ /**
+ * true if this clock is deprecated and should not be used. The ID may still show up in certain
+ * locations to help migrations, but it will not be selectable by new users.
+ */
+ val isDeprecated: Boolean = false,
+
+ /**
+ * Optional mapping of a legacy clock to a new id. This will map users that already are using
+ * `clockId` to the `replacementTarget` instead. The provider should still support the old id
+ * w/o crashing, but can consider it deprecated and the id reserved.
+ */
+ val replacementTarget: ClockId? = null,
+)
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..1fb37ec28835 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())
@@ -139,15 +137,13 @@ 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)
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..3c1adf22a405
--- /dev/null
+++ b/packages/SystemUI/plugin/src/com/android/systemui/plugins/clocks/VRect.kt
@@ -0,0 +1,188 @@
+/*
+ * 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)
+
+ 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 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)
+
+ override fun toString() = "($left, $top) -> ($right, $bottom)"
+
+ companion object {
+ val ZERO = VRect(0, 0, 0, 0)
+
+ 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/src/com/android/systemui/dagger/qualifiers/BroadcastRunning.java b/packages/SystemUI/pods/com/android/systemui/dagger/qualifiers/BroadcastRunning.java
index 5f8e54097e53..5f8e54097e53 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/qualifiers/BroadcastRunning.java
+++ b/packages/SystemUI/pods/com/android/systemui/dagger/qualifiers/BroadcastRunning.java
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/qualifiers/Default.java b/packages/SystemUI/pods/com/android/systemui/dagger/qualifiers/Default.java
index 1950d6bac251..1950d6bac251 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/qualifiers/Default.java
+++ b/packages/SystemUI/pods/com/android/systemui/dagger/qualifiers/Default.java
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/qualifiers/DisplayId.java b/packages/SystemUI/pods/com/android/systemui/dagger/qualifiers/DisplayId.java
index 155a6d224130..155a6d224130 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/qualifiers/DisplayId.java
+++ b/packages/SystemUI/pods/com/android/systemui/dagger/qualifiers/DisplayId.java
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/src/com/android/systemui/dagger/qualifiers/InstrumentationTest.java b/packages/SystemUI/pods/com/android/systemui/dagger/qualifiers/InstrumentationTest.java
index 9430445fecbf..9430445fecbf 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/qualifiers/InstrumentationTest.java
+++ b/packages/SystemUI/pods/com/android/systemui/dagger/qualifiers/InstrumentationTest.java
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/qualifiers/LongRunning.java b/packages/SystemUI/pods/com/android/systemui/dagger/qualifiers/LongRunning.java
index e90781b48f23..e90781b48f23 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/qualifiers/LongRunning.java
+++ b/packages/SystemUI/pods/com/android/systemui/dagger/qualifiers/LongRunning.java
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/src/com/android/systemui/dagger/qualifiers/NotifInflation.kt b/packages/SystemUI/pods/com/android/systemui/dagger/qualifiers/NotifInflation.kt
index 231fb2dd16c8..52a808d61adb 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/qualifiers/NotifInflation.kt
+++ b/packages/SystemUI/pods/com/android/systemui/dagger/qualifiers/NotifInflation.kt
@@ -21,4 +21,4 @@ import javax.inject.Qualifier
@Qualifier
@MustBeDocumented
@Retention(AnnotationRetention.RUNTIME)
-annotation class NotifInflation
+public annotation class NotifInflation
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/qualifiers/PerUser.java b/packages/SystemUI/pods/com/android/systemui/dagger/qualifiers/PerUser.java
index f6d5ece74ae7..f6d5ece74ae7 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/qualifiers/PerUser.java
+++ b/packages/SystemUI/pods/com/android/systemui/dagger/qualifiers/PerUser.java
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/qualifiers/RootView.java b/packages/SystemUI/pods/com/android/systemui/dagger/qualifiers/RootView.java
index e6c46c07fff8..e6c46c07fff8 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/qualifiers/RootView.java
+++ b/packages/SystemUI/pods/com/android/systemui/dagger/qualifiers/RootView.java
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/qualifiers/SystemUser.kt b/packages/SystemUI/pods/com/android/systemui/dagger/qualifiers/SystemUser.kt
index 6878a52b672d..0ea815f6ce08 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/qualifiers/SystemUser.kt
+++ b/packages/SystemUI/pods/com/android/systemui/dagger/qualifiers/SystemUser.kt
@@ -17,4 +17,7 @@ package com.android.systemui.dagger.qualifiers
import javax.inject.Qualifier
-@Qualifier @MustBeDocumented @Retention(AnnotationRetention.RUNTIME) annotation class SystemUser
+@Qualifier
+@MustBeDocumented
+@Retention(AnnotationRetention.RUNTIME)
+public annotation class SystemUser
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/qualifiers/TestHarness.java b/packages/SystemUI/pods/com/android/systemui/dagger/qualifiers/TestHarness.java
index 35c7b76a761b..35c7b76a761b 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/qualifiers/TestHarness.java
+++ b/packages/SystemUI/pods/com/android/systemui/dagger/qualifiers/TestHarness.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/dimens.xml b/packages/SystemUI/res-keyguard/values/dimens.xml
index 6d446453d9f7..24fd86076d64 100644
--- a/packages/SystemUI/res-keyguard/values/dimens.xml
+++ b/packages/SystemUI/res-keyguard/values/dimens.xml
@@ -115,6 +115,7 @@
<dimen name="below_clock_padding_end">16dp</dimen>
<dimen name="below_clock_padding_start_icons">28dp</dimen>
<dimen name="smartspace_padding_horizontal">16dp</dimen>
+ <dimen name="smartspace_padding_vertical">12dp</dimen>
<!-- Proportion of the screen height to use to set the maximum height of the bouncer to when
the device is in the DEVICE_POSTURE_HALF_OPENED posture, for the PIN/pattern entry. 0 will
diff --git a/packages/SystemUI/res/drawable/media_output_dialog_seekbar_background.xml b/packages/SystemUI/res/drawable/media_output_dialog_seekbar_background.xml
index adeb81fcf820..592217bb0e8f 100644
--- a/packages/SystemUI/res/drawable/media_output_dialog_seekbar_background.xml
+++ b/packages/SystemUI/res/drawable/media_output_dialog_seekbar_background.xml
@@ -23,16 +23,24 @@
/>
<solid android:color="@android:color/transparent" />
<size
- android:height="64dp"/>
+ android:height="@dimen/media_output_dialog_item_height"/>
+ </shape>
+ </item>
+ <item android:id="@+id/contrast_dot" android:right="8dp" android:gravity="center_vertical|end">
+ <shape android:shape="oval">
+ <solid android:color="@color/media_dialog_seekbar_progress" />
+ <size
+ android:width="4dp"
+ android:height="4dp" />
</shape>
</item>
<item android:id="@android:id/progress">
<clip>
<shape>
<corners
- android:radius="16dp"/>
+ android:radius="@dimen/media_output_dialog_background_radius"/>
<size
- android:height="64dp"/>
+ android:height="@dimen/media_output_dialog_item_height"/>
<solid android:color="@color/material_dynamic_primary80" />
</shape>
</clip>
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..d398f60ddc3c
--- /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="8dp"
+ android:insetRight="0dp"
+ android:insetBottom="8dp">
+ <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/drawable/settingslib_track_off_background.xml b/packages/SystemUI/res/drawable/settingslib_track_off_background.xml
index 3a09284d10a0..284953a2818c 100644
--- a/packages/SystemUI/res/drawable/settingslib_track_off_background.xml
+++ b/packages/SystemUI/res/drawable/settingslib_track_off_background.xml
@@ -23,4 +23,5 @@
android:right="@dimen/settingslib_switch_thumb_margin"/>
<solid android:color="@color/settingslib_track_off_color"/>
<corners android:radius="@dimen/settingslib_switch_track_radius"/>
+ <stroke android:width="2dp" android:color="@color/settingslib_track_online_color"/>
</shape>
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/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_output_list_item_advanced.xml b/packages/SystemUI/res/layout/media_output_list_item_advanced.xml
index 6b868b3c7379..ec80cf0d2f54 100644
--- a/packages/SystemUI/res/layout/media_output_list_item_advanced.xml
+++ b/packages/SystemUI/res/layout/media_output_list_item_advanced.xml
@@ -151,6 +151,8 @@
<ImageButton
android:id="@+id/end_area_image_button"
android:background="@android:color/transparent"
+ android:padding="20dp"
+ android:scaleType="fitCenter"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:contentDescription="@null"
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_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/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 4fc20e218c5e..b7455471d9a6 100644
--- a/packages/SystemUI/res/layout/volume_dialog_top_section.xml
+++ b/packages/SystemUI/res/layout/volume_dialog_top_section.xml
@@ -22,8 +22,7 @@
android:clipChildren="false"
android:clipToPadding="false"
android:gravity="center"
- android:layoutDirection="ltr"
- 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/values-af/strings.xml b/packages/SystemUI/res/values-af/strings.xml
index d8e08fc1aec3..cbec8c2e0b82 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,6 +423,12 @@
<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>
@@ -991,8 +994,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 +1003,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 +1549,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 +1573,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..655f06037922 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,6 +423,12 @@
<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>
@@ -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,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 +1549,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 +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-ar/strings.xml b/packages/SystemUI/res/values-ar/strings.xml
index 99c156b9aa2e..b2d71da5b61d 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,8 +421,14 @@
<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>
+ <!-- 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>
@@ -906,10 +906,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) -->
- <skip />
+ <string name="system_multitasking_full_screen" msgid="4221409316059910349">"استخدام وضع ملء الشاشة"</string>
+ <string name="system_multitasking_desktop_view" msgid="8829838918507805921">"استخدام وضع العرض المخصّص للكمبيوتر المكتبي"</string>
<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>
@@ -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..6aa9d66ab48f 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,6 +423,12 @@
<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>
@@ -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,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 +1549,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 +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-az/strings.xml b/packages/SystemUI/res/values-az/strings.xml
index a0eecd13b690..b87432f5d342 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,6 +423,12 @@
<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>
@@ -991,8 +994,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 +1003,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 +1549,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 +1573,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..a875e7361335 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,6 +423,12 @@
<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>
@@ -1000,6 +1003,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 +1573,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..75d0a44f38a1 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,6 +423,12 @@
<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>
@@ -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,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 +1549,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 +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-bg/strings.xml b/packages/SystemUI/res/values-bg/strings.xml
index 9d6be139efd2..4f3d64124e04 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,6 +423,12 @@
<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>
@@ -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-bn/strings.xml b/packages/SystemUI/res/values-bn/strings.xml
index 636d4f28c18a..3fd699db612f 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,6 +423,12 @@
<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>
@@ -582,7 +585,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>
@@ -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,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 +1549,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 +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-bs/strings.xml b/packages/SystemUI/res/values-bs/strings.xml
index 331bac2fb24e..0b5af1771614 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,6 +423,12 @@
<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>
@@ -991,7 +994,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 +1003,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 +1549,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 +1573,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..0225d2b27ca8 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,6 +423,12 @@
<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>
+ <!-- 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">"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>
@@ -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..8a6980e355c0 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,6 +423,12 @@
<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>
@@ -991,8 +994,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 +1003,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 +1549,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 +1573,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..60c5c1c5e1c0 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,6 +423,12 @@
<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>
@@ -991,8 +994,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 +1003,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 +1549,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 +1573,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..66b60b18cdae 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,6 +423,12 @@
<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>
@@ -991,8 +994,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 +1003,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 +1549,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 +1573,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..639aee2d038e 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,6 +423,12 @@
<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>
@@ -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-en-rAU/strings.xml b/packages/SystemUI/res/values-en-rAU/strings.xml
index c2537110ba58..62441c8682f3 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,6 +423,12 @@
<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>
+ <!-- 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">"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>
@@ -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,8 @@
<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>
+ <!-- no translation found for accessibility_qs_edit_tile_already_added (5900071201690226752) -->
+ <skip />
<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 +1549,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 +1573,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..69aca1fb223e 100644
--- a/packages/SystemUI/res/values-en-rCA/strings.xml
+++ b/packages/SystemUI/res/values-en-rCA/strings.xml
@@ -423,6 +423,9 @@
<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>
@@ -997,6 +1000,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 +1569,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..62441c8682f3 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,6 +423,12 @@
<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>
+ <!-- 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">"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>
@@ -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,8 @@
<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>
+ <!-- no translation found for accessibility_qs_edit_tile_already_added (5900071201690226752) -->
+ <skip />
<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 +1549,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 +1573,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..62441c8682f3 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,6 +423,12 @@
<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>
+ <!-- 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">"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>
@@ -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,8 @@
<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>
+ <!-- no translation found for accessibility_qs_edit_tile_already_added (5900071201690226752) -->
+ <skip />
<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 +1549,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 +1573,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..840afd45cdf2 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,6 +423,12 @@
<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>
+ <!-- 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 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>
@@ -1000,6 +1003,8 @@
<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>
+ <!-- no translation found for accessibility_qs_edit_tile_already_added (5900071201690226752) -->
+ <skip />
<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 +1573,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..177f298d0121 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,6 +423,12 @@
<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>
@@ -588,7 +591,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>
@@ -991,8 +994,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 +1003,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 +1549,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 +1573,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..be021fc96f9d 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,6 +423,12 @@
<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>
@@ -991,8 +994,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 +1003,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 +1549,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 +1573,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..edcc17225ace 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,6 +423,12 @@
<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>
@@ -991,8 +994,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 +1003,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 +1549,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 +1573,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..f1f6a14568a5 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,6 +423,12 @@
<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>
@@ -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-fi/strings.xml b/packages/SystemUI/res/values-fi/strings.xml
index fa9468df5fa0..747659956dbf 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,6 +425,12 @@
<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>
@@ -993,8 +996,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 +1005,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 +1551,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 +1575,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..e22f306b0d57 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,6 +423,12 @@
<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>
@@ -991,8 +994,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 +1003,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">"Position incorrecte."</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">"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 +1549,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 +1573,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..cceab1c3689b 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,6 +423,12 @@
<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>
@@ -1000,6 +1003,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 +1573,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..f54a634eaa18 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,6 +423,12 @@
<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>
@@ -1000,6 +1003,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 +1573,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..1830b347f829 100644
--- a/packages/SystemUI/res/values-gu/strings.xml
+++ b/packages/SystemUI/res/values-gu/strings.xml
@@ -426,6 +426,12 @@
<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>
@@ -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-hi/strings.xml b/packages/SystemUI/res/values-hi/strings.xml
index 8878c1448abf..644980062f9f 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,6 +423,12 @@
<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>
@@ -606,7 +609,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>
@@ -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,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 +1549,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 +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-hr/strings.xml b/packages/SystemUI/res/values-hr/strings.xml
index 054f3947ff4d..7db95fe316ce 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,6 +423,12 @@
<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>
@@ -1000,6 +1003,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 +1573,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..906ce54f33fd 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,6 +423,12 @@
<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>
@@ -991,8 +994,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 +1003,8 @@
<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>
+ <!-- no translation found for accessibility_qs_edit_tile_already_added (5900071201690226752) -->
+ <skip />
<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 +1549,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 +1573,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..57a822e138c2 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,6 +423,12 @@
<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>
@@ -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-in/strings.xml b/packages/SystemUI/res/values-in/strings.xml
index 6ef3cdc65e69..2d38c8fa8e7a 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,6 +423,12 @@
<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>
@@ -991,8 +994,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 +1003,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 +1549,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 +1573,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..5c37bdb40161 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,6 +423,12 @@
<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>
+ <!-- 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">"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>
@@ -1000,6 +1003,8 @@
<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>
+ <!-- no translation found for accessibility_qs_edit_tile_already_added (5900071201690226752) -->
+ <skip />
<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 +1573,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..a21b6dbac247 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,6 +423,12 @@
<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>
+ <!-- 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">"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>
@@ -1000,6 +1003,8 @@
<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>
+ <!-- no translation found for accessibility_qs_edit_tile_already_added (5900071201690226752) -->
+ <skip />
<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 +1573,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..936b10e520d5 100644
--- a/packages/SystemUI/res/values-iw/strings.xml
+++ b/packages/SystemUI/res/values-iw/strings.xml
@@ -245,12 +245,9 @@
<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>
@@ -354,7 +351,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,6 +426,12 @@
<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>
@@ -589,7 +592,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,10 +909,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) -->
- <skip />
+ <string name="system_multitasking_full_screen" msgid="4221409316059910349">"שימוש במסך מלא"</string>
+ <string name="system_multitasking_desktop_view" msgid="8829838918507805921">"שימוש בתצוגה למחשב"</string>
<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>
@@ -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..d5ecfe538b89 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,6 +423,12 @@
<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>
@@ -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-ka/strings.xml b/packages/SystemUI/res/values-ka/strings.xml
index d9a5824653c1..2e3dc98eb153 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,6 +423,12 @@
<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>
@@ -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-kk/strings.xml b/packages/SystemUI/res/values-kk/strings.xml
index 141e99c10854..1c4d31a6b758 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>
@@ -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,6 +423,12 @@
<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>
@@ -586,7 +589,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>
@@ -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,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 +1549,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 +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-km/strings.xml b/packages/SystemUI/res/values-km/strings.xml
index 9323308b67b6..8b3bfcc1d30b 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,6 +423,12 @@
<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>
@@ -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-kn/strings.xml b/packages/SystemUI/res/values-kn/strings.xml
index d7f8b89878f9..50f8c60537ba 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,6 +423,12 @@
<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>
@@ -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-ko/strings.xml b/packages/SystemUI/res/values-ko/strings.xml
index 0c43ea1de5c2..7342f32f116e 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,6 +423,12 @@
<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>
@@ -586,7 +589,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>
@@ -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,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 +1549,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 +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-ky/strings.xml b/packages/SystemUI/res/values-ky/strings.xml
index b411bdcaf466..f21ee1a401b7 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,6 +423,12 @@
<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>
@@ -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,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 +1549,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 +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-lo/strings.xml b/packages/SystemUI/res/values-lo/strings.xml
index e9e585eda056..4c778d1ac869 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,6 +423,12 @@
<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>
@@ -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..f387a4ed4178 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,6 +423,12 @@
<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>
+ <!-- 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š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>
@@ -1000,6 +1003,8 @@
<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>
+ <!-- no translation found for accessibility_qs_edit_tile_already_added (5900071201690226752) -->
+ <skip />
<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 +1573,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..fd549219134f 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,6 +423,12 @@
<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>
@@ -1000,6 +1003,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 +1573,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..2afe93ea7aae 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,6 +423,12 @@
<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>
@@ -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,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 +1549,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 +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-ml/strings.xml b/packages/SystemUI/res/values-ml/strings.xml
index aae5ac48f64c..48d07cb2ae28 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,6 +423,12 @@
<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>
@@ -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-mn/strings.xml b/packages/SystemUI/res/values-mn/strings.xml
index 59388935394c..c7fb7da28ead 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,6 +423,12 @@
<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>
@@ -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,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 +1549,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 +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-mr/strings.xml b/packages/SystemUI/res/values-mr/strings.xml
index c3c716055bb0..2c5a9a85845d 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,6 +423,12 @@
<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>
@@ -696,7 +699,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 +744,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 +784,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>
@@ -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-ms/strings.xml b/packages/SystemUI/res/values-ms/strings.xml
index 3c96d0a502a8..557509860f07 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,6 +423,12 @@
<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>
+ <!-- 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">"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>
@@ -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..b428422e5f07 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,6 +423,12 @@
<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>
@@ -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,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 +1549,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 +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-nb/strings.xml b/packages/SystemUI/res/values-nb/strings.xml
index 1c655f1859ad..95740758c6e7 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,6 +423,12 @@
<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>
@@ -991,8 +994,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 +1003,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 +1549,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 +1573,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..4d37f8fd760b 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,6 +423,12 @@
<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>
@@ -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,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 +1549,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 +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-nl/strings.xml b/packages/SystemUI/res/values-nl/strings.xml
index 388190702462..26d87522595b 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,6 +423,12 @@
<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>
+ <!-- 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">"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>
@@ -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..a0e360cf6bcb 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,6 +423,12 @@
<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>
@@ -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,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 +1549,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 +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-pa/strings.xml b/packages/SystemUI/res/values-pa/strings.xml
index 60275382cabe..f2a6612bdb8a 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,6 +423,12 @@
<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>
@@ -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-pl/strings.xml b/packages/SystemUI/res/values-pl/strings.xml
index 35ff6c941b07..99e99e1a2e6a 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,6 +423,12 @@
<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>
@@ -582,13 +585,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>
@@ -991,8 +994,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 +1003,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 +1549,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 +1573,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..4663dba04bc2 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,6 +423,12 @@
<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>
@@ -991,8 +994,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 +1003,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 +1549,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 +1573,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..7a0e4f6e68b2 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,6 +423,12 @@
<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>
+ <!-- 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 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>
@@ -1000,6 +1003,8 @@
<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>
+ <!-- no translation found for accessibility_qs_edit_tile_already_added (5900071201690226752) -->
+ <skip />
<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 +1573,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..4663dba04bc2 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,6 +423,12 @@
<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>
@@ -991,8 +994,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 +1003,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 +1549,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 +1573,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..e8b826b940f4 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,6 +423,12 @@
<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>
@@ -991,8 +994,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 +1003,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 +1549,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 +1573,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..52458ebd176d 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,6 +423,12 @@
<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>
@@ -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..84a981f9db89 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,6 +423,12 @@
<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>
@@ -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,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 +1549,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 +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-sk/strings.xml b/packages/SystemUI/res/values-sk/strings.xml
index 868e9390e5ef..78a332a6c45c 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,6 +423,12 @@
<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>
+ <!-- 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">"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>
@@ -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,8 @@
<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>
+ <!-- no translation found for accessibility_qs_edit_tile_already_added (5900071201690226752) -->
+ <skip />
<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 +1549,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 +1573,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..93649f5039d0 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,6 +423,12 @@
<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>
+ <!-- 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">"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>
@@ -991,8 +994,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 +1003,8 @@
<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>
+ <!-- no translation found for accessibility_qs_edit_tile_already_added (5900071201690226752) -->
+ <skip />
<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 +1549,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 +1573,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..5e69808d7f63 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,6 +423,12 @@
<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>
@@ -991,8 +994,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 +1003,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 +1549,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 +1573,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..1b645a8e2017 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,6 +423,12 @@
<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>
@@ -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-sv/strings.xml b/packages/SystemUI/res/values-sv/strings.xml
index 9f2a2083175b..a4129e0afb18 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,6 +423,12 @@
<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>
@@ -1000,6 +1003,8 @@
<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>
+ <!-- no translation found for accessibility_qs_edit_tile_already_added (5900071201690226752) -->
+ <skip />
<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 +1573,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..cb82afff7f91 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,6 +423,12 @@
<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>
@@ -991,8 +994,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 +1003,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 +1549,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 +1573,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..77be6ea6a64b 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,6 +423,12 @@
<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>
@@ -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,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 +1549,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 +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-te/strings.xml b/packages/SystemUI/res/values-te/strings.xml
index 2042ead675d6..b4665190f33d 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,6 +423,12 @@
<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>
@@ -1001,6 +1004,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>
@@ -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-th/strings.xml b/packages/SystemUI/res/values-th/strings.xml
index 9ac9d01b8ee7..e816c04a5e65 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,6 +423,12 @@
<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>
@@ -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,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 +1549,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 +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-tl/strings.xml b/packages/SystemUI/res/values-tl/strings.xml
index 516386bd3c9b..d996218c64db 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,6 +423,12 @@
<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>
@@ -1000,6 +1003,8 @@
<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>
+ <!-- no translation found for accessibility_qs_edit_tile_already_added (5900071201690226752) -->
+ <skip />
<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 +1573,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..9d789417b09f 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,6 +423,12 @@
<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>
@@ -991,8 +994,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 +1003,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 +1549,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 +1573,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..97e5bb70313f 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,6 +423,12 @@
<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>
@@ -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,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 +1549,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 +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-ur/strings.xml b/packages/SystemUI/res/values-ur/strings.xml
index ffc3f5e7c8aa..41ba6b235284 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,6 +423,12 @@
<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>
@@ -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-uz/strings.xml b/packages/SystemUI/res/values-uz/strings.xml
index e325cf33adb3..f713ddc13b80 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,6 +423,12 @@
<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>
@@ -991,8 +994,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 +1003,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 +1549,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 +1573,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..2f266a781525 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,6 +423,12 @@
<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>
@@ -586,7 +589,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>
@@ -991,8 +994,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 +1003,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 +1549,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 +1573,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..480debcb239b 100644
--- a/packages/SystemUI/res/values-zh-rCN/strings.xml
+++ b/packages/SystemUI/res/values-zh-rCN/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,6 +423,12 @@
<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>
@@ -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,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 +1549,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 +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-zh-rHK/strings.xml b/packages/SystemUI/res/values-zh-rHK/strings.xml
index c5a81fb25a9b..bc9e0c580901 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,6 +423,12 @@
<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>
@@ -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-zh-rTW/strings.xml b/packages/SystemUI/res/values-zh-rTW/strings.xml
index 24a15a14f1b3..02fda5a68313 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,6 +423,12 @@
<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>
@@ -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-zu/strings.xml b/packages/SystemUI/res/values-zu/strings.xml
index 918864886d21..886cbdf2b097 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,6 +425,12 @@
<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>
@@ -993,8 +996,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 +1005,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 +1551,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 +1575,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..78e719f6289a 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 -->
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 6ff1240c5e60..8c1fd65d96d4 100644
--- a/packages/SystemUI/res/values/strings.xml
+++ b/packages/SystemUI/res/values/strings.xml
@@ -1024,6 +1024,12 @@
<string name="hearing_devices_ambient_control_left">Left</string>
<!-- QuickSettings: The text to show the control is for right side device. [CHAR LIMIT=30] -->
<string name="hearing_devices_ambient_control_right">Right</string>
+ <!-- QuickSettings: Content description for unified ambient control slider. [CHAR LIMIT=NONE] -->
+ <string name="hearing_devices_ambient_control_description">Surroundings</string>
+ <!-- QuickSettings: Content description for left ambient control slider. [CHAR LIMIT=NONE] -->
+ <string name="hearing_devices_ambient_control_left_description">Left surroundings</string>
+ <!-- QuickSettings: Content description for left ambient control slider. [CHAR LIMIT=NONE] -->
+ <string name="hearing_devices_ambient_control_right_description">Right surroundings</string>
<!-- QuickSettings: Content description for a button, that expands ambient volume sliders [CHAR_LIMIT=NONE] -->
<string name="hearing_devices_ambient_expand_controls">Expand to left and right separated controls</string>
<!-- QuickSettings: Content description for a button, that collapses ambient volume sliders [CHAR LIMIT=NONE] -->
@@ -1036,6 +1042,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>
@@ -2349,8 +2357,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] -->
@@ -2593,6 +2601,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>
diff --git a/packages/SystemUI/res/values/styles.xml b/packages/SystemUI/res/values/styles.xml
index 5b96e5085fd2..7895ff7b90f6 100644
--- a/packages/SystemUI/res/values/styles.xml
+++ b/packages/SystemUI/res/values/styles.xml
@@ -871,6 +871,7 @@
</style>
<style name="MediaPlayer.SessionAction.Primary" parent="MediaPlayer.SessionAction">
+ <item name="android:tint">@android:color/system_on_primary_dark</item>
<item name="android:background">@drawable/qs_media_round_button_background</item>
<item name="android:backgroundTint">@color/media_player_solid_button_bg</item>
</style>
@@ -894,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/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 1b8282bf9b93..763b1072f968 100644
--- a/packages/SystemUI/src/com/android/keyguard/ClockEventController.kt
+++ b/packages/SystemUI/src/com/android/keyguard/ClockEventController.kt
@@ -55,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
@@ -78,7 +80,7 @@ import javax.inject.Inject
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.DisposableHandle
import kotlinx.coroutines.Job
-import kotlinx.coroutines.flow.combine
+import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.filter
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.merge
@@ -147,7 +149,7 @@ constructor(
val clockStr = clock.toString()
loggers.forEach { it.d({ "New Clock: $str1" }) { str1 = clockStr } }
- clock.initialize(isDarkTheme(), dozeAmount, 0f)
+ clock.initialize(isDarkTheme(), dozeAmount.value, 0f, clockListener)
if (!regionSamplingEnabled) {
updateColors()
@@ -240,17 +242,16 @@ constructor(
private var smallClockFrame: ViewGroup? = null
private var onGlobalLayoutListener: OnGlobalLayoutListener? = null
- private var isDozing = false
- private set
-
private var isCharging = false
- private var dozeAmount = 0f
private var isKeyguardVisible = false
private var isRegistered = false
private var disposableHandle: DisposableHandle? = null
private val regionSamplingEnabled = featureFlags.isEnabled(REGION_SAMPLING)
private var largeClockOnSecondaryDisplay = false
+ val dozeAmount = MutableStateFlow(0f)
+ val onClockBoundsChanged = MutableStateFlow<VRectF>(VRectF.ZERO)
+
private fun isDarkTheme(): Boolean {
val isLightTheme = TypedValue()
context.theme.resolveAttribute(android.R.attr.isLightTheme, isLightTheme, true)
@@ -306,12 +307,19 @@ constructor(
var smallTimeListener: TimeListener? = null
var largeTimeListener: TimeListener? = null
val shouldTimeListenerRun: Boolean
- get() = isKeyguardVisible && dozeAmount < DOZE_TICKRATE_THRESHOLD
+ get() = isKeyguardVisible && dozeAmount.value < DOZE_TICKRATE_THRESHOLD
private var weatherData: WeatherData? = null
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 =
@@ -466,7 +474,6 @@ constructor(
disposableHandle =
parent.repeatWhenAttached {
repeatOnLifecycle(Lifecycle.State.CREATED) {
- listenForDozing(this)
if (ModesUi.isEnabled) {
listenForDnd(this)
}
@@ -576,17 +583,17 @@ constructor(
}
private fun handleDoze(doze: Float) {
- dozeAmount = doze
clock?.run {
Trace.beginSection("$TAG#smallClock.animations.doze")
- smallClock.animations.doze(dozeAmount)
+ smallClock.animations.doze(doze)
Trace.endSection()
Trace.beginSection("$TAG#largeClock.animations.doze")
- largeClock.animations.doze(dozeAmount)
+ largeClock.animations.doze(doze)
Trace.endSection()
}
smallTimeListener?.update(doze < DOZE_TICKRATE_THRESHOLD)
largeTimeListener?.update(doze < DOZE_TICKRATE_THRESHOLD)
+ dozeAmount.value = doze
}
@VisibleForTesting
@@ -642,18 +649,6 @@ constructor(
}
}
- @VisibleForTesting
- internal fun listenForDozing(scope: CoroutineScope): Job {
- return scope.launch {
- combine(keyguardInteractor.dozeAmount, keyguardInteractor.isDozing) {
- localDozeAmount,
- localIsDozing ->
- localDozeAmount > dozeAmount || localIsDozing
- }
- .collect { localIsDozing -> isDozing = localIsDozing }
- }
- }
-
class TimeListener(val clockFace: ClockFaceController, val executor: DelayableExecutor) {
val predrawListener =
ViewTreeObserver.OnPreDrawListener {
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/KeyguardSecurityContainer.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainer.java
index 73dc28230e65..e2f3955263a1 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainer.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainer.java
@@ -172,6 +172,7 @@ public class KeyguardSecurityContainer extends ConstraintLayout {
private boolean mIsDragging;
private float mStartTouchY = -1;
private boolean mDisappearAnimRunning;
+ private boolean mIsAppearAnimationDelayed;
private SwipeListener mSwipeListener;
private ViewMode mViewMode = new DefaultViewMode();
private boolean mIsInteractable;
@@ -583,6 +584,10 @@ public class KeyguardSecurityContainer extends ConstraintLayout {
return false;
}
+ boolean isAppearAnimationDelayed() {
+ return mIsAppearAnimationDelayed;
+ }
+
void addMotionEventListener(Gefingerpoken listener) {
mMotionEventListeners.add(listener);
}
@@ -624,6 +629,19 @@ public class KeyguardSecurityContainer extends ConstraintLayout {
mViewMode.startAppearAnimation(securityMode);
}
+ /**
+ * Set view translationY and alpha as we delay bouncer animation.
+ */
+ public void setupForDelayedAppear() {
+ setTranslationY(0f);
+ setAlpha(0f);
+ setIsAppearAnimationDelayed(true);
+ }
+
+ public void setIsAppearAnimationDelayed(boolean isDelayed) {
+ mIsAppearAnimationDelayed = isDelayed;
+ }
+
private void beginJankInstrument(int cuj) {
KeyguardInputView securityView = mSecurityViewFlipper.getSecurityView();
if (securityView == null) return;
@@ -812,6 +830,7 @@ public class KeyguardSecurityContainer extends ConstraintLayout {
public void reset() {
mViewMode.reset();
mDisappearAnimRunning = false;
+ mIsAppearAnimationDelayed = false;
}
/**
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java
index d10fce416150..198c1cb08647 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java
@@ -18,6 +18,7 @@ package com.android.keyguard;
import static android.app.StatusBarManager.SESSION_KEYGUARD;
import static android.content.res.Configuration.ORIENTATION_LANDSCAPE;
+import static android.content.res.Configuration.ORIENTATION_PORTRAIT;
import static com.android.keyguard.KeyguardSecurityContainer.BOUNCER_DISMISSIBLE_KEYGUARD;
import static com.android.keyguard.KeyguardSecurityContainer.BOUNCER_DISMISS_BIOMETRIC;
@@ -385,6 +386,10 @@ public class KeyguardSecurityContainerController extends ViewController<Keyguard
boolean useSplitBouncer = orientation == ORIENTATION_LANDSCAPE;
mSecurityViewFlipperController.updateConstraints(useSplitBouncer);
}
+ if (orientation == ORIENTATION_PORTRAIT) {
+ // If there is any delayed bouncer appear animation it can start now
+ startAppearAnimationIfDelayed();
+ }
}
@Override
@@ -845,6 +850,16 @@ public class KeyguardSecurityContainerController extends ViewController<Keyguard
}
}
+ /** Start appear animation which was previously delayed from opening bouncer in landscape. */
+ public void startAppearAnimationIfDelayed() {
+ if (!mView.isAppearAnimationDelayed()) {
+ return;
+ }
+ setAlpha(1f);
+ appear();
+ mView.setIsAppearAnimationDelayed(false);
+ }
+
/** Called when the bouncer changes visibility. */
public void onBouncerVisibilityChanged(boolean isVisible) {
if (!isVisible) {
@@ -1301,4 +1316,13 @@ public class KeyguardSecurityContainerController extends ViewController<Keyguard
setAlpha(MathUtils.constrain(1 - scaledFraction, 0f, 1f));
mView.setTranslationY(scaledFraction * mTranslationY);
}
+
+ /** Set up view for delayed appear animation. */
+ public void setupForDelayedAppear() {
+ mView.setupForDelayedAppear();
+ }
+
+ public boolean isLandscapeOrientation() {
+ return mLastOrientation == Configuration.ORIENTATION_LANDSCAPE;
+ }
}
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..d84b034eade8 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
@@ -2923,13 +2923,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/AmbientVolumeLayout.java b/packages/SystemUI/src/com/android/systemui/accessibility/hearingaid/AmbientVolumeLayout.java
index 5247acc94e03..33c9eb17cd44 100644
--- a/packages/SystemUI/src/com/android/systemui/accessibility/hearingaid/AmbientVolumeLayout.java
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/hearingaid/AmbientVolumeLayout.java
@@ -327,8 +327,19 @@ public class AmbientVolumeLayout extends LinearLayout implements AmbientVolumeUi
slider.addOnChangeListener(mSliderOnChangeListener);
if (side == SIDE_LEFT) {
slider.setTitle(mContext.getString(R.string.hearing_devices_ambient_control_left));
+ slider.setContentDescription(
+ mContext.getString(R.string.hearing_devices_ambient_control_left));
+ slider.setSliderContentDescription(
+ mContext.getString(R.string.hearing_devices_ambient_control_left_description));
} else if (side == SIDE_RIGHT) {
slider.setTitle(mContext.getString(R.string.hearing_devices_ambient_control_right));
+ slider.setContentDescription(
+ mContext.getString(R.string.hearing_devices_ambient_control_right));
+ slider.setSliderContentDescription(
+ mContext.getString(R.string.hearing_devices_ambient_control_right_description));
+ } else {
+ slider.setSliderContentDescription(
+ mContext.getString(R.string.hearing_devices_ambient_control_description));
}
mSideToSliderMap.put(side, slider);
}
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/hearingaid/AmbientVolumeSlider.java b/packages/SystemUI/src/com/android/systemui/accessibility/hearingaid/AmbientVolumeSlider.java
index 1a068c4229c9..5c0ad3dcf421 100644
--- a/packages/SystemUI/src/com/android/systemui/accessibility/hearingaid/AmbientVolumeSlider.java
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/hearingaid/AmbientVolumeSlider.java
@@ -92,6 +92,11 @@ public class AmbientVolumeSlider extends LinearLayout {
mSlider = requireViewById(R.id.ambient_volume_slider);
mSlider.addOnSliderTouchListener(mSliderTouchListener);
mSlider.addOnChangeListener(mSliderChangeListener);
+
+ setFocusable(false);
+ setClickable(false);
+ mSlider.setFocusable(false);
+ mSlider.setClickable(false);
}
/**
@@ -178,6 +183,13 @@ public class AmbientVolumeSlider extends LinearLayout {
return (int) Math.ceil((value - min) / levelGap);
}
+ /** Sets the content description to the ambient volume slider. */
+ public void setSliderContentDescription(CharSequence contentDescription) {
+ if (mSlider != null) {
+ mSlider.setContentDescription(contentDescription);
+ }
+ }
+
/** Interface definition for a callback invoked when a slider's value is changed. */
public interface OnChangeListener {
/** Called when the finger is take off from the slider. */
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..fb3bc620ee68 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;
@@ -140,12 +144,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 +168,8 @@ public class HearingDevicesDialogDelegate implements SystemUIDialog.Delegate,
@Main Executor mainExecutor,
@Background Executor bgExecutor,
AudioManager audioManager,
- HearingDevicesUiEventLogger uiEventLogger) {
+ HearingDevicesUiEventLogger uiEventLogger,
+ QSSettingsPackageRepository qsSettingsPackageRepository) {
mShowPairNewDevice = showPairNewDevice;
mSystemUIDialogFactory = systemUIDialogFactory;
mActivityStarter = activityStarter;
@@ -181,6 +181,7 @@ public class HearingDevicesDialogDelegate implements SystemUIDialog.Delegate,
mProfileManager = localBluetoothManager.getProfileManager();
mUiEventLogger = uiEventLogger;
mLaunchSourceId = launchSourceId;
+ mQSSettingsPackageRepository = qsSettingsPackageRepository;
}
@Override
@@ -196,11 +197,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));
}
@@ -263,6 +264,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,
@@ -385,8 +400,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 +522,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/HearingDevicesUiEvent.kt b/packages/SystemUI/src/com/android/systemui/accessibility/hearingaid/HearingDevicesUiEvent.kt
index fe1d5040c6f5..4a695d638713 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,9 @@ 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 = "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/ambient/touch/BouncerSwipeTouchHandler.kt b/packages/SystemUI/src/com/android/systemui/ambient/touch/BouncerSwipeTouchHandler.kt
index d8e7a168ef3c..97de78c41af7 100644
--- a/packages/SystemUI/src/com/android/systemui/ambient/touch/BouncerSwipeTouchHandler.kt
+++ b/packages/SystemUI/src/com/android/systemui/ambient/touch/BouncerSwipeTouchHandler.kt
@@ -18,6 +18,7 @@ package com.android.systemui.ambient.touch
import android.animation.Animator
import android.animation.AnimatorListenerAdapter
import android.animation.ValueAnimator
+import android.content.res.Configuration
import android.graphics.Rect
import android.graphics.Region
import android.util.Log
@@ -36,6 +37,7 @@ import com.android.systemui.ambient.touch.dagger.BouncerSwipeModule
import com.android.systemui.ambient.touch.scrim.ScrimController
import com.android.systemui.ambient.touch.scrim.ScrimManager
import com.android.systemui.bouncer.shared.constants.KeyguardBouncerConstants
+import com.android.systemui.communal.domain.interactor.CommunalSettingsInteractor
import com.android.systemui.communal.ui.viewmodel.CommunalViewModel
import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor
import com.android.systemui.plugins.ActivityStarter
@@ -46,6 +48,7 @@ import com.android.systemui.shade.ShadeExpansionChangeEvent
import com.android.systemui.shade.data.repository.ShadeRepository
import com.android.systemui.statusbar.NotificationShadeWindowController
import com.android.systemui.statusbar.phone.CentralSurfaces
+import com.android.systemui.statusbar.policy.KeyguardStateController
import com.android.wm.shell.animation.FlingAnimationUtils
import java.util.Optional
import javax.inject.Inject
@@ -82,6 +85,8 @@ constructor(
private val sceneInteractor: SceneInteractor,
private val shadeRepository: ShadeRepository,
private val windowRootViewProvider: Optional<Provider<WindowRootView>>,
+ private val keyguardStateController: KeyguardStateController,
+ communalSettingsInteractor: CommunalSettingsInteractor,
) : TouchHandler {
/** An interface for creating ValueAnimators. */
interface ValueAnimatorCreator {
@@ -101,6 +106,8 @@ constructor(
private var capture: Boolean? = null
private var expanded: Boolean = false
private var touchSession: TouchSession? = null
+ private var isUserTrackingExpansionDisabled: Boolean = false
+ private var isKeyguardScreenRotationAllowed: Boolean = false
private val scrimManagerCallback =
ScrimManager.Callback { controller ->
currentScrimController?.reset()
@@ -121,6 +128,9 @@ constructor(
distanceX: Float,
distanceY: Float,
): Boolean {
+ val isLandscape =
+ windowRootView.resources.configuration.orientation ==
+ Configuration.ORIENTATION_LANDSCAPE
if (capture == null) {
capture =
if (Flags.dreamOverlayBouncerSwipeDirectionFiltering()) {
@@ -137,7 +147,9 @@ constructor(
// reset expanding
expanded = false
// Since the user is dragging the bouncer up, set scrimmed to false.
- currentScrimController?.show()
+ if (isKeyguardScreenRotationAllowed || !isLandscape) {
+ currentScrimController?.show(false)
+ }
if (SceneContainerFlag.isEnabled) {
sceneInteractor.onRemoteUserInputStarted("bouncer touch handler")
@@ -172,6 +184,37 @@ constructor(
return true
}
+ if (touchSession == null) {
+ return true
+ }
+ val screenTravelPercentage =
+ (abs((y - e2.y).toDouble()) / touchSession!!.bounds.height()).toFloat()
+
+ if (communalSettingsInteractor.isV2FlagEnabled()) {
+ if (isUserTrackingExpansionDisabled) return true
+ // scrolling up in landscape orientation but device doesn't allow keyguard
+ // screen rotation
+ if (y > e2.y && !isKeyguardScreenRotationAllowed && isLandscape) {
+ velocityTracker!!.computeCurrentVelocity(1000)
+ currentExpansion = 1 - screenTravelPercentage
+ expanded =
+ shouldExpandBouncer(
+ velocityTracker!!.yVelocity,
+ velocityTracker!!.xVelocity,
+ EXPANSION_FROM_LANDSCAPE_THRESHOLD,
+ currentExpansion,
+ )
+ if (expanded) {
+ // Once scroll past the percentage threshold, show bouncer scrimmed,
+ // so that user won't be required to drag up and then right to keep
+ // bouncer open after screen rotates to portrait.
+ currentScrimController?.show(true)
+ isUserTrackingExpansionDisabled = true
+ }
+ return true
+ }
+ }
+
if (SceneContainerFlag.isEnabled) {
windowRootView.dispatchTouchEvent(e2)
} else {
@@ -182,12 +225,7 @@ constructor(
// is fully hidden at full expansion (1) and fully visible when fully
// collapsed
// (0).
- touchSession?.apply {
- val screenTravelPercentage =
- (abs((this@outer.y - e2.y).toDouble()) / getBounds().height())
- .toFloat()
- setPanelExpansion(1 - screenTravelPercentage)
- }
+ touchSession?.apply { setPanelExpansion(1 - screenTravelPercentage) }
}
}
@@ -262,6 +300,7 @@ constructor(
}
scrimManager.addCallback(scrimManagerCallback)
currentScrimController = scrimManager.currentController
+ isKeyguardScreenRotationAllowed = keyguardStateController.isKeyguardScreenRotationAllowed()
shadeRepository.setLegacyShadeTracking(true)
session.registerCallback {
@@ -271,6 +310,7 @@ constructor(
scrimManager.removeCallback(scrimManagerCallback)
capture = null
touchSession = null
+ isUserTrackingExpansionDisabled = false
if (!Flags.communalBouncerDoNotModifyPluginOpen()) {
notificationShadeWindowController.setForcePluginOpen(false, this)
}
@@ -299,14 +339,25 @@ constructor(
return
}
+ // We are already in progress of opening bouncer scrimmed
+ if (isUserTrackingExpansionDisabled) {
+ // User is done scrolling, reset
+ isUserTrackingExpansionDisabled = false
+ return
+ }
+
// We must capture the resulting velocities as resetMonitor() will clear these
// values.
velocityTracker!!.computeCurrentVelocity(1000)
val verticalVelocity = velocityTracker!!.yVelocity
- val horizontalVelocity = velocityTracker!!.xVelocity
- val velocityVector =
- hypot(horizontalVelocity.toDouble(), verticalVelocity.toDouble()).toFloat()
- expanded = !flingRevealsOverlay(verticalVelocity, velocityVector)
+ expanded =
+ shouldExpandBouncer(
+ verticalVelocity,
+ velocityTracker!!.xVelocity,
+ FLING_PERCENTAGE_THRESHOLD,
+ currentExpansion,
+ )
+
val expansion =
if (expanded!!) KeyguardBouncerConstants.EXPANSION_VISIBLE
else KeyguardBouncerConstants.EXPANSION_HIDDEN
@@ -339,11 +390,27 @@ constructor(
return animator
}
- protected fun flingRevealsOverlay(velocity: Float, velocityVector: Float): Boolean {
+ private fun shouldExpandBouncer(
+ verticalVelocity: Float,
+ horizontalVelocity: Float,
+ threshold: Float,
+ expansion: Float,
+ ): Boolean {
+ val velocityVector =
+ hypot(horizontalVelocity.toDouble(), verticalVelocity.toDouble()).toFloat()
+ return !flingRevealsOverlay(verticalVelocity, velocityVector, threshold, expansion)
+ }
+
+ protected fun flingRevealsOverlay(
+ velocity: Float,
+ velocityVector: Float,
+ threshold: Float,
+ expansion: Float,
+ ): Boolean {
// Fully expand the space above the bouncer, if the user has expanded the bouncer less
// than halfway or final velocity was positive, indicating a downward direction.
return if (abs(velocityVector.toDouble()) < flingAnimationUtils.minVelocityPxPerSecond) {
- currentExpansion > FLING_PERCENTAGE_THRESHOLD
+ expansion > threshold
} else {
velocity > 0
}
@@ -390,6 +457,7 @@ constructor(
companion object {
const val FLING_PERCENTAGE_THRESHOLD = 0.5f
+ const val EXPANSION_FROM_LANDSCAPE_THRESHOLD = 0.95f
private const val TAG = "BouncerSwipeTouchHandler"
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/ambient/touch/scrim/BouncerScrimController.java b/packages/SystemUI/src/com/android/systemui/ambient/touch/scrim/BouncerScrimController.java
index 94c998267598..6f2dd799c409 100644
--- a/packages/SystemUI/src/com/android/systemui/ambient/touch/scrim/BouncerScrimController.java
+++ b/packages/SystemUI/src/com/android/systemui/ambient/touch/scrim/BouncerScrimController.java
@@ -33,8 +33,8 @@ public class BouncerScrimController implements ScrimController {
}
@Override
- public void show() {
- mStatusBarKeyguardViewManager.showPrimaryBouncer(false);
+ public void show(boolean scrimmed) {
+ mStatusBarKeyguardViewManager.showPrimaryBouncer(scrimmed);
}
@Override
diff --git a/packages/SystemUI/src/com/android/systemui/ambient/touch/scrim/ScrimController.java b/packages/SystemUI/src/com/android/systemui/ambient/touch/scrim/ScrimController.java
index 00543523ec2e..90cbd258f03e 100644
--- a/packages/SystemUI/src/com/android/systemui/ambient/touch/scrim/ScrimController.java
+++ b/packages/SystemUI/src/com/android/systemui/ambient/touch/scrim/ScrimController.java
@@ -25,8 +25,9 @@ import com.android.systemui.shade.ShadeExpansionChangeEvent;
public interface ScrimController {
/**
* Called at the start of expansion before any expansion amount updates.
+ * @param scrimmed true when the bouncer should show scrimmed, false when user will be dragging.
*/
- default void show() {
+ default void show(boolean scrimmed) {
}
/**
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/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 bbf9a19012a4..30b98a658821 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
@@ -137,6 +137,9 @@ constructor(
)
bind(overlayView!!, overlayViewModel, windowManager.get())
overlayView!!.visibility = View.INVISIBLE
+ overlayView!!.setOnClickListener { v ->
+ v.requireViewById<LottieAnimationView>(R.id.sidefps_animation).toggleAnimation()
+ }
Log.d(TAG, "show(): adding overlayView $overlayView")
windowManager.get().addView(overlayView, overlayViewModel.defaultOverlayViewParams)
}
@@ -234,3 +237,11 @@ private fun LottieAnimationView.addOverlayDynamicColor(colorCallbacks: List<Lott
resumeAnimation()
}
}
+
+fun LottieAnimationView.toggleAnimation() {
+ if (isAnimating) {
+ pauseAnimation()
+ } else {
+ resumeAnimation()
+ }
+}
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/AudioSharingDeviceItemActionInteractorImpl.kt b/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/AudioSharingDeviceItemActionInteractorImpl.kt
index 94fca218c74f..f8f692dd9033 100644
--- a/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/AudioSharingDeviceItemActionInteractorImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/AudioSharingDeviceItemActionInteractorImpl.kt
@@ -124,10 +124,18 @@ constructor(
when (deviceItem.type) {
DeviceItemType.AUDIO_SHARING_MEDIA_BLUETOOTH_DEVICE -> {
uiEventLogger.log(BluetoothTileDialogUiEvent.CHECK_MARK_ACTION_BUTTON_CLICKED)
+ logger.logAudioSharingButtonClick(
+ AudioSharingButtonClick.CHECK_MARK,
+ deviceItem,
+ )
audioSharingInteractor.stopAudioSharing()
}
DeviceItemType.AVAILABLE_AUDIO_SHARING_MEDIA_BLUETOOTH_DEVICE -> {
uiEventLogger.log(BluetoothTileDialogUiEvent.PLUS_ACTION_BUTTON_CLICKED)
+ logger.logAudioSharingButtonClick(
+ AudioSharingButtonClick.PLUS_BUTTON,
+ deviceItem,
+ )
audioSharingInteractor.startAudioSharing()
}
else -> {
diff --git a/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/AudioSharingInteractor.kt b/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/AudioSharingInteractor.kt
index 832afb1799b1..7a76eed57095 100644
--- a/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/AudioSharingInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/AudioSharingInteractor.kt
@@ -40,6 +40,7 @@ import kotlinx.coroutines.flow.flowOn
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.mapNotNull
import kotlinx.coroutines.flow.merge
+import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.flow.receiveAsFlow
import kotlinx.coroutines.withContext
@@ -73,6 +74,7 @@ constructor(
private val context: Context,
private val localBluetoothManager: LocalBluetoothManager?,
private val audioSharingRepository: AudioSharingRepository,
+ private val logger: BluetoothTileDialogLogger,
@Background private val backgroundDispatcher: CoroutineDispatcher,
) : AudioSharingInteractor {
@@ -92,9 +94,12 @@ constructor(
override val audioSourceStateUpdate =
isAudioSharingOn
+ .onEach { logger.logAudioSharingStateChanged(it) }
.flatMapLatest {
if (it) {
- audioSharingRepository.audioSourceStateUpdate
+ audioSharingRepository.audioSourceStateUpdate.onEach {
+ logger.logAudioSourceStateUpdate()
+ }
} else {
emptyFlow()
}
diff --git a/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/AudioSharingRepository.kt b/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/AudioSharingRepository.kt
index 44f9769f5930..d84b34a5a933 100644
--- a/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/AudioSharingRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/AudioSharingRepository.kt
@@ -53,6 +53,7 @@ interface AudioSharingRepository {
class AudioSharingRepositoryImpl(
private val localBluetoothManager: LocalBluetoothManager,
private val settingsLibAudioSharingRepository: SettingsLibAudioSharingRepository,
+ private val logger: BluetoothTileDialogLogger,
@Background private val backgroundDispatcher: CoroutineDispatcher,
) : AudioSharingRepository {
@@ -79,7 +80,11 @@ class AudioSharingRepositoryImpl(
}
leAudioBroadcastProfile?.latestBluetoothLeBroadcastMetadata?.let { metadata ->
leAudioBroadcastAssistantProfile?.let {
- it.allConnectedDevices.forEach { sink -> it.addSource(sink, metadata, false) }
+ it.allConnectedDevices.forEach { sink ->
+ it.addSource(sink, metadata, false).also {
+ logger.logAudioSharingRequest(AudioSharingRequest.ADD_SOURCE)
+ }
+ }
}
}
}
@@ -99,7 +104,9 @@ class AudioSharingRepositoryImpl(
if (!settingsLibAudioSharingRepository.audioSharingAvailable()) {
return@withContext
}
- leAudioBroadcastProfile?.startPrivateBroadcast()
+ leAudioBroadcastProfile?.startPrivateBroadcast().also {
+ logger.logAudioSharingRequest(AudioSharingRequest.START_BROADCAST)
+ }
}
}
@@ -108,7 +115,9 @@ class AudioSharingRepositoryImpl(
if (!settingsLibAudioSharingRepository.audioSharingAvailable()) {
return@withContext
}
- leAudioBroadcastProfile?.stopLatestBroadcast()
+ leAudioBroadcastProfile?.stopLatestBroadcast().also {
+ logger.logAudioSharingRequest(AudioSharingRequest.STOP_BROADCAST)
+ }
}
}
}
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 576acd2e5304..5a5a51e53d63 100644
--- a/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/BluetoothDetailsContentManager.kt
+++ b/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/BluetoothDetailsContentManager.kt
@@ -218,7 +218,7 @@ constructor(
scrollViewContent.layoutParams.height = WRAP_CONTENT
lastUiUpdateMs = systemClock.elapsedRealtime()
lastItemRow = itemRow
- logger.logDeviceUiUpdate(lastUiUpdateMs - start)
+ logger.logDeviceUiUpdate(lastUiUpdateMs - start, deviceItem)
}
}
}
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/BluetoothTileDialogLogger.kt b/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/BluetoothTileDialogLogger.kt
index 06116f0a21c3..5f866c5522f6 100644
--- a/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/BluetoothTileDialogLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/BluetoothTileDialogLogger.kt
@@ -27,18 +27,29 @@ private const val TAG = "BluetoothTileDialogLog"
enum class BluetoothStateStage {
USER_TOGGLED,
BLUETOOTH_STATE_VALUE_SET,
- BLUETOOTH_STATE_CHANGE_RECEIVED
+ BLUETOOTH_STATE_CHANGE_RECEIVED,
}
enum class DeviceFetchTrigger {
FIRST_LOAD,
BLUETOOTH_STATE_CHANGE_RECEIVED,
- BLUETOOTH_CALLBACK_RECEIVED
+ BLUETOOTH_CALLBACK_RECEIVED,
+}
+
+enum class AudioSharingButtonClick {
+ PLUS_BUTTON,
+ CHECK_MARK,
+}
+
+enum class AudioSharingRequest {
+ START_BROADCAST,
+ STOP_BROADCAST,
+ ADD_SOURCE,
}
enum class JobStatus {
FINISHED,
- CANCELLED
+ CANCELLED,
}
class BluetoothTileDialogLogger
@@ -53,7 +64,7 @@ constructor(@BluetoothTileDialogLog private val logBuffer: LogBuffer) {
str1 = stage.toString()
str2 = state
},
- { "BluetoothState. stage=$str1 state=$str2" }
+ { "BluetoothState. stage=$str1 state=$str2" },
)
fun logDeviceClick(address: String, type: DeviceItemType) =
@@ -64,7 +75,7 @@ constructor(@BluetoothTileDialogLog private val logBuffer: LogBuffer) {
str1 = address
str2 = type.toString()
},
- { "DeviceClick. address=$str1 type=$str2" }
+ { "DeviceClick. address=$str1 type=$str2" },
)
fun logActiveDeviceChanged(address: String?, profileId: Int) =
@@ -75,7 +86,7 @@ constructor(@BluetoothTileDialogLog private val logBuffer: LogBuffer) {
str1 = address
int1 = profileId
},
- { "ActiveDeviceChanged. address=$str1 profileId=$int1" }
+ { "ActiveDeviceChanged. address=$str1 profileId=$int1" },
)
fun logProfileConnectionStateChanged(address: String, state: String, profileId: Int) =
@@ -87,7 +98,7 @@ constructor(@BluetoothTileDialogLog private val logBuffer: LogBuffer) {
str2 = state
int1 = profileId
},
- { "ProfileConnectionStateChanged. address=$str1 state=$str2 profileId=$int1" }
+ { "ProfileConnectionStateChanged. address=$str1 state=$str2 profileId=$int1" },
)
fun logBatteryChanged(address: String, key: Int, value: ByteArray?) =
@@ -99,7 +110,7 @@ constructor(@BluetoothTileDialogLog private val logBuffer: LogBuffer) {
int1 = key
str2 = value?.toString() ?: ""
},
- { "BatteryChanged. address=$str1 key=$int1 value=$str2" }
+ { "BatteryChanged. address=$str1 key=$int1 value=$str2" },
)
fun logDeviceFetch(status: JobStatus, trigger: DeviceFetchTrigger, duration: Long) =
@@ -111,18 +122,26 @@ constructor(@BluetoothTileDialogLog private val logBuffer: LogBuffer) {
str2 = trigger.toString()
long1 = duration
},
- { "DeviceFetch. status=$str1 trigger=$str2 duration=$long1" }
+ { "DeviceFetch. status=$str1 trigger=$str2 duration=$long1" },
)
- fun logDeviceUiUpdate(duration: Long) =
- logBuffer.log(TAG, DEBUG, { long1 = duration }, { "DeviceUiUpdate. duration=$long1" })
+ fun logDeviceUiUpdate(duration: Long, deviceItem: List<DeviceItem>) =
+ logBuffer.log(
+ TAG,
+ DEBUG,
+ {
+ long1 = duration
+ str1 = deviceItem.toString()
+ },
+ { "DeviceUiUpdate. duration=$long1 deviceItem=$str1" },
+ )
fun logDeviceClickInAudioSharingWhenEnabled(inAudioSharing: Boolean) {
logBuffer.log(
TAG,
DEBUG,
{ str1 = inAudioSharing.toString() },
- { "DeviceClick. in audio sharing=$str1" }
+ { "DeviceClick. in audio sharing=$str1" },
)
}
@@ -138,7 +157,36 @@ constructor(@BluetoothTileDialogLog private val logBuffer: LogBuffer) {
str1 = criteria
str2 = deviceItem.toString()
},
- { "$str1. deviceItem=$str2" }
+ { "$str1. deviceItem=$str2" },
)
}
+
+ fun logAudioSharingStateChanged(stateOn: Boolean) =
+ logBuffer.log(
+ TAG,
+ DEBUG,
+ { str1 = stateOn.toString() },
+ { "AudioSharingStateChanged. state=$str1" },
+ )
+
+ fun logAudioSourceStateUpdate() = logBuffer.log(TAG, DEBUG, {}, { "AudioSourceStateUpdate" })
+
+ fun logAudioSharingButtonClick(click: AudioSharingButtonClick, deviceItem: DeviceItem) =
+ logBuffer.log(
+ TAG,
+ DEBUG,
+ {
+ str1 = click.toString()
+ str2 = deviceItem.toString()
+ },
+ { "AudioSharingButtonClick. click=$str1 deviceItem=$str2" },
+ )
+
+ fun logAudioSharingRequest(apiCall: AudioSharingRequest) =
+ logBuffer.log(
+ TAG,
+ DEBUG,
+ { str1 = apiCall.toString() },
+ { "AudioSharingRequest. apiCall=$str1" },
+ )
}
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/dagger/AudioSharingModule.kt b/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/dagger/AudioSharingModule.kt
index afe9a1eec0b6..01c4b21bfd9f 100644
--- a/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/dagger/AudioSharingModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/dagger/AudioSharingModule.kt
@@ -30,6 +30,7 @@ import com.android.systemui.bluetooth.qsdialog.AudioSharingRepositoryEmptyImpl
import com.android.systemui.bluetooth.qsdialog.AudioSharingRepositoryImpl
import com.android.systemui.bluetooth.qsdialog.AvailableAudioSharingMediaDeviceItemFactory
import com.android.systemui.bluetooth.qsdialog.AvailableMediaDeviceItemFactory
+import com.android.systemui.bluetooth.qsdialog.BluetoothTileDialogLogger
import com.android.systemui.bluetooth.qsdialog.ConnectedDeviceItemFactory
import com.android.systemui.bluetooth.qsdialog.DeviceItemActionInteractor
import com.android.systemui.bluetooth.qsdialog.DeviceItemActionInteractorImpl
@@ -53,6 +54,7 @@ interface AudioSharingModule {
fun provideAudioSharingRepository(
localBluetoothManager: LocalBluetoothManager?,
settingsLibAudioSharingRepository: SettingsLibAudioSharingRepository,
+ logger: BluetoothTileDialogLogger,
@Background backgroundDispatcher: CoroutineDispatcher,
): AudioSharingRepository =
if (
@@ -62,6 +64,7 @@ interface AudioSharingModule {
AudioSharingRepositoryImpl(
localBluetoothManager,
settingsLibAudioSharingRepository,
+ logger,
backgroundDispatcher,
)
} else {
diff --git a/packages/SystemUI/src/com/android/systemui/bouncer/domain/interactor/BouncerInteractor.kt b/packages/SystemUI/src/com/android/systemui/bouncer/domain/interactor/BouncerInteractor.kt
index 75503e8575f0..b26a2c08532e 100644
--- a/packages/SystemUI/src/com/android/systemui/bouncer/domain/interactor/BouncerInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/bouncer/domain/interactor/BouncerInteractor.kt
@@ -17,7 +17,9 @@
package com.android.systemui.bouncer.domain.interactor
import android.app.StatusBarManager.SESSION_KEYGUARD
+import com.android.app.tracing.FlowTracing.traceAsCounter
import com.android.app.tracing.coroutines.asyncTraced as async
+import com.android.compose.animation.scene.ObservableTransitionState
import com.android.compose.animation.scene.SceneKey
import com.android.internal.logging.UiEventLogger
import com.android.systemui.authentication.domain.interactor.AuthenticationInteractor
@@ -38,9 +40,12 @@ import com.android.systemui.deviceentry.domain.interactor.DeviceEntryFaceAuthInt
import com.android.systemui.log.SessionTracker
import com.android.systemui.power.domain.interactor.PowerInteractor
import com.android.systemui.scene.domain.interactor.SceneBackInteractor
+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.ShadeDisplayAware
+import com.android.systemui.utils.coroutines.flow.flatMapLatestConflated
import javax.inject.Inject
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.flow.Flow
@@ -49,7 +54,9 @@ import kotlinx.coroutines.flow.SharedFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.flow.combine
+import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.filter
+import kotlinx.coroutines.flow.flowOf
import kotlinx.coroutines.flow.map
/** Encapsulates business logic and application state accessing use-cases. */
@@ -65,6 +72,7 @@ constructor(
private val powerInteractor: PowerInteractor,
private val uiEventLogger: UiEventLogger,
private val sessionTracker: SessionTracker,
+ sceneInteractor: SceneInteractor,
sceneBackInteractor: SceneBackInteractor,
@ShadeDisplayAware private val configurationInteractor: ConfigurationInteractor,
) {
@@ -149,6 +157,31 @@ constructor(
val dismissDestination: Flow<SceneKey> =
sceneBackInteractor.backScene.map { it ?: Scenes.Lockscreen }
+ /** The amount [0-1] that the Bouncer Overlay has been transitioned to. */
+ val bouncerExpansion: Flow<Float> =
+ if (SceneContainerFlag.isEnabled) {
+ sceneInteractor.transitionState.flatMapLatestConflated { state ->
+ when (state) {
+ is ObservableTransitionState.Idle ->
+ flowOf(if (Overlays.Bouncer in state.currentOverlays) 1f else 0f)
+ is ObservableTransitionState.Transition ->
+ if (state.toContent == Overlays.Bouncer) {
+ state.progress
+ } else if (state.fromContent == Overlays.Bouncer) {
+ state.progress.map { progress -> 1 - progress }
+ } else {
+ state.currentOverlays().map {
+ if (Overlays.Bouncer in it) 1f else 0f
+ }
+ }
+ }
+ }
+ } else {
+ flowOf()
+ }
+ .distinctUntilChanged()
+ .traceAsCounter("bouncer_expansion") { (it * 100f).toInt() }
+
/** Notifies that the user has places down a pointer, not necessarily dragging just yet. */
fun onDown() {
falsingInteractor.avoidGesture()
diff --git a/packages/SystemUI/src/com/android/systemui/bouncer/ui/binder/BouncerViewBinder.kt b/packages/SystemUI/src/com/android/systemui/bouncer/ui/binder/BouncerViewBinder.kt
index 7f268315e566..5d64219c7f90 100644
--- a/packages/SystemUI/src/com/android/systemui/bouncer/ui/binder/BouncerViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/bouncer/ui/binder/BouncerViewBinder.kt
@@ -15,6 +15,7 @@ import com.android.systemui.bouncer.ui.viewmodel.KeyguardBouncerViewModel
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor
+import com.android.systemui.keyguard.ui.viewmodel.GlanceableHubToPrimaryBouncerTransitionViewModel
import com.android.systemui.keyguard.ui.viewmodel.PrimaryBouncerToGoneTransitionViewModel
import com.android.systemui.log.BouncerLogger
import com.android.systemui.user.domain.interactor.SelectedUserInteractor
@@ -30,6 +31,8 @@ data class LegacyBouncerDependencies
constructor(
val viewModel: KeyguardBouncerViewModel,
val primaryBouncerToGoneTransitionViewModel: PrimaryBouncerToGoneTransitionViewModel,
+ val glanceableHubToPrimaryBouncerTransitionViewModel:
+ GlanceableHubToPrimaryBouncerTransitionViewModel,
val componentFactory: KeyguardBouncerComponent.Factory,
val messageAreaControllerFactory: KeyguardMessageAreaController.Factory,
val bouncerMessageInteractor: BouncerMessageInteractor,
@@ -82,6 +85,7 @@ constructor(
view,
deps.viewModel,
deps.primaryBouncerToGoneTransitionViewModel,
+ deps.glanceableHubToPrimaryBouncerTransitionViewModel,
deps.componentFactory,
deps.messageAreaControllerFactory,
deps.bouncerMessageInteractor,
diff --git a/packages/SystemUI/src/com/android/systemui/bouncer/ui/binder/KeyguardBouncerViewBinder.kt b/packages/SystemUI/src/com/android/systemui/bouncer/ui/binder/KeyguardBouncerViewBinder.kt
index 7d8945a5b4a7..45f0e13c185e 100644
--- a/packages/SystemUI/src/com/android/systemui/bouncer/ui/binder/KeyguardBouncerViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/bouncer/ui/binder/KeyguardBouncerViewBinder.kt
@@ -33,6 +33,7 @@ import com.android.systemui.bouncer.domain.interactor.BouncerMessageInteractor
import com.android.systemui.bouncer.shared.constants.KeyguardBouncerConstants.EXPANSION_VISIBLE
import com.android.systemui.bouncer.ui.BouncerViewDelegate
import com.android.systemui.bouncer.ui.viewmodel.KeyguardBouncerViewModel
+import com.android.systemui.keyguard.ui.viewmodel.GlanceableHubToPrimaryBouncerTransitionViewModel
import com.android.systemui.keyguard.ui.viewmodel.PrimaryBouncerToGoneTransitionViewModel
import com.android.systemui.lifecycle.repeatWhenAttached
import com.android.systemui.log.BouncerLogger
@@ -49,6 +50,8 @@ object KeyguardBouncerViewBinder {
view: ViewGroup,
viewModel: KeyguardBouncerViewModel,
primaryBouncerToGoneTransitionViewModel: PrimaryBouncerToGoneTransitionViewModel,
+ glanceableHubToPrimaryBouncerTransitionViewModel:
+ GlanceableHubToPrimaryBouncerTransitionViewModel,
componentFactory: KeyguardBouncerComponent.Factory,
messageAreaControllerFactory: KeyguardMessageAreaController.Factory,
bouncerMessageInteractor: BouncerMessageInteractor,
@@ -133,7 +136,20 @@ object KeyguardBouncerViewBinder {
/* turningOff= */ false
)
securityContainerController.setInitialMessage()
- securityContainerController.appear()
+ // Delay bouncer appearing animation when opening it from the
+ // glanceable hub in landscape, until after orientation changes
+ // to portrait. This prevents bouncer from showing in landscape
+ // layout, if bouncer rotation is not allowed.
+ if (
+ glanceableHubToPrimaryBouncerTransitionViewModel
+ .willDelayAppearAnimation(
+ securityContainerController.isLandscapeOrientation
+ )
+ ) {
+ securityContainerController.setupForDelayedAppear()
+ } else {
+ securityContainerController.appear()
+ }
securityContainerController.onResume(
KeyguardSecurityView.SCREEN_ON
)
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 b13f6df3f4f5..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
@@ -70,6 +72,7 @@ import androidx.compose.ui.unit.DpSize
import androidx.compose.ui.unit.dp
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import com.android.app.tracing.coroutines.launchTraced as launch
+import com.android.compose.modifiers.padding
import com.android.compose.ui.graphics.drawInOverlay
import com.android.systemui.Flags
import com.android.systemui.biometrics.Utils.toBitmap
@@ -164,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,
@@ -327,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.
@@ -348,7 +346,23 @@ fun BrightnessSliderContainer(
DisposableEffect(Unit) { onDispose { viewModel.setIsDragging(false) } }
- Box(modifier = modifier.fillMaxWidth().sysuiResTag("brightness_slider")) {
+ 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
+ .padding(vertical = { SliderBackgroundFrameSize.height.roundToPx() })
+ .fillMaxWidth()
+ .sysuiResTag("brightness_slider")
+ ) {
BrightnessSlider(
gammaValue = gamma,
valueRange = viewModel.minBrightness.value..viewModel.maxBrightness.value,
@@ -358,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 =
@@ -390,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/clipboardoverlay/ActionIntentCreator.kt b/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ActionIntentCreator.kt
new file mode 100644
index 000000000000..df6c1b18e3e9
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ActionIntentCreator.kt
@@ -0,0 +1,99 @@
+/*
+ * 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.res.R
+import javax.inject.Inject
+
+@SysUISingleton
+class ActionIntentCreator @Inject constructor() : 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)
+ }
+
+ override 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 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..314b6e7f5a28 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,19 @@ public class ClipboardOverlayController implements ClipboardListener.ClipboardOv
private void editImage(Uri uri) {
mClipboardLogger.logSessionComplete(CLIPBOARD_OVERLAY_EDIT_TAPPED);
- mContext.startActivity(IntentCreator.getImageEditIntent(uri, mContext));
+ mContext.startActivity(mIntentCreator.getImageEditIntent(uri, mContext));
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 +717,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 +744,11 @@ 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.getImageEditIntent(mClipboardModel.getUri(), mContext));
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..4b24536ad28f
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/clipboardoverlay/DefaultIntentCreator.java
@@ -0,0 +1,102 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF 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 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 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;
+ }
+
+ 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..c8a6b05f090b 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,13 @@
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;
-
-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);
+ Intent getImageEditIntent(Uri uri, Context context);
+ 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/communal/CommunalSceneStartable.kt b/packages/SystemUI/src/com/android/systemui/communal/CommunalSceneStartable.kt
index e36e85565293..49b0bb63545f 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/CommunalSceneStartable.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/CommunalSceneStartable.kt
@@ -29,10 +29,17 @@ import com.android.systemui.communal.shared.model.CommunalScenes
import com.android.systemui.communal.shared.model.CommunalScenes.isCommunal
import com.android.systemui.communal.shared.model.CommunalTransitionKeys
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.keyguard.domain.interactor.KeyguardInteractor
+import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
+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.GLANCEABLE_HUB
+import com.android.systemui.scene.shared.model.Scenes
import com.android.systemui.statusbar.NotificationShadeWindowController
+import com.android.systemui.util.kotlin.BooleanFlowOperators.anyOf
import com.android.systemui.util.kotlin.emitOnStart
import com.android.systemui.util.kotlin.sample
import com.android.systemui.util.settings.SettingsProxyExt.observerFlow
@@ -45,6 +52,7 @@ import kotlinx.coroutines.Job
import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.collectLatest
import kotlinx.coroutines.flow.combine
+import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.withContext
@@ -60,10 +68,12 @@ constructor(
private val communalInteractor: CommunalInteractor,
private val communalSettingsInteractor: CommunalSettingsInteractor,
private val communalSceneInteractor: CommunalSceneInteractor,
+ private val keyguardTransitionInteractor: KeyguardTransitionInteractor,
private val keyguardInteractor: KeyguardInteractor,
private val systemSettings: SystemSettings,
private val notificationShadeWindowController: NotificationShadeWindowController,
@Background private val bgScope: CoroutineScope,
+ @Application private val applicationScope: CoroutineScope,
@Main private val mainDispatcher: CoroutineDispatcher,
private val uiEventLogger: UiEventLogger,
) : CoreStartable {
@@ -154,6 +164,25 @@ constructor(
}
}
}
+
+ if (communalSettingsInteractor.isV2FlagEnabled()) {
+ applicationScope.launch(context = mainDispatcher) {
+ anyOf(
+ communalSceneInteractor.isTransitioningToOrIdleOnCommunal,
+ // when transitioning from hub to dream, allow hub to stay at the current
+ // orientation, as keyguard doesn't allow rotation by default.
+ keyguardTransitionInteractor.isInTransition(
+ edge = Edge.create(from = Scenes.Communal, to = DREAMING),
+ edgeWithoutSceneContainer =
+ Edge.create(from = GLANCEABLE_HUB, to = DREAMING),
+ ),
+ )
+ .distinctUntilChanged()
+ .collectLatest {
+ notificationShadeWindowController.setGlanceableHubOrientationAware(it)
+ }
+ }
+ }
}
private fun cancelHubTimeout() {
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/domain/interactor/CommunalSceneInteractor.kt b/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalSceneInteractor.kt
index 3d9e93036dbc..fed99d71fa3b 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalSceneInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalSceneInteractor.kt
@@ -307,6 +307,21 @@ constructor(
initialValue = false,
)
+ /** Flow that emits a boolean if transitioning to or idle on communal scene. */
+ val isTransitioningToOrIdleOnCommunal: Flow<Boolean> =
+ transitionState
+ .map {
+ (it is ObservableTransitionState.Idle &&
+ it.currentScene == CommunalScenes.Communal) ||
+ (it is ObservableTransitionState.Transition &&
+ it.toContent == CommunalScenes.Communal)
+ }
+ .stateIn(
+ scope = applicationScope,
+ started = SharingStarted.WhileSubscribed(),
+ initialValue = false,
+ )
+
private companion object {
const val TAG = "CommunalSceneInteractor"
}
diff --git a/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalToDreamButtonViewModel.kt b/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalToDreamButtonViewModel.kt
deleted file mode 100644
index b531d159acde..000000000000
--- a/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalToDreamButtonViewModel.kt
+++ /dev/null
@@ -1,124 +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.ui.viewmodel
-
-import android.annotation.SuppressLint
-import android.app.DreamManager
-import android.content.Intent
-import android.provider.Settings
-import androidx.compose.runtime.getValue
-import com.android.internal.logging.UiEventLogger
-import com.android.systemui.communal.domain.interactor.CommunalPrefsInteractor
-import com.android.systemui.communal.domain.interactor.CommunalSettingsInteractor
-import com.android.systemui.communal.shared.log.CommunalUiEvent
-import com.android.systemui.dagger.qualifiers.Background
-import com.android.systemui.lifecycle.ExclusiveActivatable
-import com.android.systemui.lifecycle.Hydrator
-import com.android.systemui.plugins.ActivityStarter
-import com.android.systemui.statusbar.policy.BatteryController
-import com.android.systemui.util.kotlin.BooleanFlowOperators.allOf
-import com.android.systemui.util.kotlin.BooleanFlowOperators.not
-import com.android.systemui.util.kotlin.isDevicePluggedIn
-import com.android.systemui.util.kotlin.sample
-import dagger.assisted.AssistedFactory
-import dagger.assisted.AssistedInject
-import kotlin.coroutines.CoroutineContext
-import kotlinx.coroutines.awaitCancellation
-import kotlinx.coroutines.channels.Channel
-import kotlinx.coroutines.coroutineScope
-import kotlinx.coroutines.flow.collectLatest
-import kotlinx.coroutines.flow.distinctUntilChanged
-import kotlinx.coroutines.flow.receiveAsFlow
-import kotlinx.coroutines.launch
-import kotlinx.coroutines.withContext
-
-class CommunalToDreamButtonViewModel
-@AssistedInject
-constructor(
- @Background private val backgroundContext: CoroutineContext,
- batteryController: BatteryController,
- private val prefsInteractor: CommunalPrefsInteractor,
- private val settingsInteractor: CommunalSettingsInteractor,
- private val activityStarter: ActivityStarter,
- private val dreamManager: DreamManager,
- private val uiEventLogger: UiEventLogger,
-) : ExclusiveActivatable() {
-
- private val hydrator = Hydrator("CommunalToDreamButtonViewModel.hydrator")
- private val _requests = Channel<Unit>(Channel.BUFFERED)
-
- /** Whether we should show a button on hub to switch to dream. */
- val shouldShowDreamButtonOnHub: Boolean by
- hydrator.hydratedStateOf(
- traceName = "shouldShowDreamButtonOnHub",
- initialValue = false,
- source = batteryController.isDevicePluggedIn().distinctUntilChanged(),
- )
-
- /** Return whether to show the dream button tooltip. */
- val shouldShowTooltip: Boolean by
- hydrator.hydratedStateOf(
- traceName = "shouldShowTooltip",
- initialValue = false,
- source =
- allOf(
- not(prefsInteractor.isDreamButtonTooltipDismissed),
- prefsInteractor.isHubOnboardingDismissed,
- ),
- )
-
- /** Set the dream button tooltip to be dismissed. */
- fun setDreamButtonTooltipDismissed() {
- prefsInteractor.setDreamButtonTooltipDismissed()
- }
-
- /** Handle a tap on the "show dream" button. */
- fun onShowDreamButtonTap() {
- uiEventLogger.log(CommunalUiEvent.COMMUNAL_HUB_SHOW_DREAM_BUTTON_TAP)
- _requests.trySend(Unit)
- }
-
- @SuppressLint("MissingPermission")
- override suspend fun onActivated(): Nothing = coroutineScope {
- launch {
- _requests
- .receiveAsFlow()
- .sample(settingsInteractor.isScreensaverEnabled)
- .collectLatest { enabled ->
- withContext(backgroundContext) {
- if (enabled) {
- dreamManager.startDream()
- } else {
- activityStarter.postStartActivityDismissingKeyguard(
- Intent(Settings.ACTION_DREAM_SETTINGS),
- 0,
- )
- }
- }
- }
- }
-
- launch { hydrator.activate() }
-
- awaitCancellation()
- }
-
- @AssistedFactory
- interface Factory {
- fun create(): CommunalToDreamButtonViewModel
- }
-}
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..62a98d7a48ea 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
@@ -367,11 +367,24 @@ constructor(
/** See [CommunalSettingsInteractor.isV2FlagEnabled] */
fun v2FlagEnabled(): Boolean = communalSettingsInteractor.isV2FlagEnabled()
- val swipeToHubEnabled: StateFlow<Boolean> by lazy {
+ val swipeToHubEnabled: Flow<Boolean> by lazy {
+ val inAllowedDeviceState =
+ if (swipeToHub) {
+ MutableStateFlow(true)
+ } else if (v2FlagEnabled()) {
+ communalInteractor.shouldShowCommunal
+ } else {
+ MutableStateFlow(false)
+ }
+
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/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/dagger/qualifiers/AdditionalStartable.java b/packages/SystemUI/src/com/android/systemui/dagger/qualifiers/AdditionalStartable.java
deleted file mode 100644
index e69de29bb2d1..000000000000
--- a/packages/SystemUI/src/com/android/systemui/dagger/qualifiers/AdditionalStartable.java
+++ /dev/null
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/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/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/PerDisplayRepository.kt b/packages/SystemUI/src/com/android/systemui/display/data/repository/PerDisplayRepository.kt
new file mode 100644
index 000000000000..d1d013542fbf
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/display/data/repository/PerDisplayRepository.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.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.Dumpable
+import com.android.systemui.dagger.qualifiers.Background
+import com.android.systemui.dump.DumpManager
+import dagger.assisted.Assisted
+import dagger.assisted.AssistedFactory
+import dagger.assisted.AssistedInject
+import java.io.PrintWriter
+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
+}
+
+/**
+ * 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 dumpManager: DumpManager,
+) : PerDisplayRepository<T>, Dumpable {
+
+ private val perDisplayInstances = ConcurrentHashMap<Int, T?>()
+
+ init {
+ backgroundApplicationScope.launch("$debugName#start") { start() }
+ }
+
+ private suspend fun start() {
+ dumpManager.registerNormalDumpable("PerDisplayRepository-${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 dump(pw: PrintWriter, args: Array<out String>) {
+ pw.println(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/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/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/grid/ui/compose/SpannedGrids.kt b/packages/SystemUI/src/com/android/systemui/grid/ui/compose/SpannedGrids.kt
index 96ef03c996a9..e06c228e24b2 100644
--- a/packages/SystemUI/src/com/android/systemui/grid/ui/compose/SpannedGrids.kt
+++ b/packages/SystemUI/src/com/android/systemui/grid/ui/compose/SpannedGrids.kt
@@ -13,9 +13,9 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-
package com.android.systemui.grid.ui.compose
+import androidx.collection.IntIntPair
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.BoxScope
@@ -24,7 +24,6 @@ import androidx.compose.runtime.key
import androidx.compose.runtime.remember
import androidx.compose.ui.Modifier
import androidx.compose.ui.layout.Layout
-import androidx.compose.ui.layout.Placeable
import androidx.compose.ui.semantics.CollectionInfo
import androidx.compose.ui.semantics.CollectionItemInfo
import androidx.compose.ui.semantics.collectionInfo
@@ -34,6 +33,8 @@ import androidx.compose.ui.unit.Constraints
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.LayoutDirection
import androidx.compose.ui.unit.dp
+import androidx.compose.ui.util.fastForEachIndexed
+import androidx.compose.ui.util.fastMapIndexed
import kotlin.math.max
/**
@@ -65,7 +66,11 @@ fun HorizontalSpannedGrid(
spans: List<Int>,
modifier: Modifier = Modifier,
keys: (spanIndex: Int) -> Any = { it },
- composables: @Composable BoxScope.(spanIndex: Int) -> Unit,
+ composables:
+ @Composable
+ BoxScope.(
+ spanIndex: Int, row: Int, isFirstInColumn: Boolean, isLastInColumn: Boolean,
+ ) -> Unit,
) {
SpannedGrid(
primarySpaces = rows,
@@ -80,7 +85,7 @@ fun HorizontalSpannedGrid(
}
/**
- * Horizontal (non lazy) grid that supports [spans] for its elements.
+ * Vertical (non lazy) grid that supports [spans] for its elements.
*
* The elements will be laid down horizontally first, and then by rows. So assuming LTR layout, it
* will be (for a span list `[2, 1, 2, 1, 1, 1, 1, 1]` and 4 columns):
@@ -107,7 +112,9 @@ fun VerticalSpannedGrid(
spans: List<Int>,
modifier: Modifier = Modifier,
keys: (spanIndex: Int) -> Any = { it },
- composables: @Composable BoxScope.(spanIndex: Int) -> Unit,
+ composables:
+ @Composable
+ BoxScope.(spanIndex: Int, column: Int, isFirstInRow: Boolean, isLastInRow: Boolean) -> Unit,
) {
SpannedGrid(
primarySpaces = columns,
@@ -130,7 +137,9 @@ private fun SpannedGrid(
isVertical: Boolean,
modifier: Modifier = Modifier,
keys: (spanIndex: Int) -> Any = { it },
- composables: @Composable BoxScope.(spanIndex: Int) -> Unit,
+ composables:
+ @Composable
+ BoxScope.(spanIndex: Int, secondaryAxis: Int, isFirst: Boolean, isLast: Boolean) -> Unit,
) {
val crossAxisArrangement = Arrangement.spacedBy(crossAxisSpacing)
spans.forEachIndexed { index, span ->
@@ -139,7 +148,6 @@ private fun SpannedGrid(
"expected rance of [1, $primarySpaces]"
}
}
-
if (isVertical) {
check(crossAxisSpacing >= 0.dp) { "Negative columnSpacing $crossAxisSpacing" }
check(mainAxisSpacing >= 0.dp) { "Negative rowSpacing $mainAxisSpacing" }
@@ -147,29 +155,30 @@ private fun SpannedGrid(
check(mainAxisSpacing >= 0.dp) { "Negative columnSpacing $mainAxisSpacing" }
check(crossAxisSpacing >= 0.dp) { "Negative rowSpacing $crossAxisSpacing" }
}
-
- val totalMainAxisGroups: Int =
+ // List of primary axis index to secondary axis index
+ // This is keyed to the size of the spans list for performance reasons as we don't expect the
+ // spans value to change outside of edit mode.
+ val positions = remember(spans.size) { Array(spans.size) { IntIntPair(0, 0) } }
+ val totalMainAxisGroups =
remember(primarySpaces, spans) {
- var currentAccumulated = 0
- var groups = 1
- spans.forEach { span ->
- if (currentAccumulated + span <= primarySpaces) {
- currentAccumulated += span
- } else {
- groups += 1
- currentAccumulated = span
+ var mainAxisGroup = 0
+ var currentSlot = 0
+ spans.fastForEachIndexed { index, span ->
+ if (currentSlot + span > primarySpaces) {
+ currentSlot = 0
+ mainAxisGroup += 1
}
+ positions[index] = IntIntPair(mainAxisGroup, currentSlot)
+ currentSlot += span
}
- groups
+ mainAxisGroup + 1
}
-
val slotPositionsAndSizesCache = remember {
object {
var sizes = IntArray(0)
var positions = IntArray(0)
}
}
-
Layout(
{
(0 until spans.size).map { spanIndex ->
@@ -184,7 +193,13 @@ private fun SpannedGrid(
}
}
) {
- composables(spanIndex)
+ val position = positions[spanIndex]
+ composables(
+ spanIndex,
+ position.second,
+ position.second == 0,
+ positions.getOrNull(spanIndex + 1)?.first != position.first,
+ )
}
}
}
@@ -205,7 +220,6 @@ private fun SpannedGrid(
slotPositionsAndSizesCache.sizes,
)
val cellSizesInCrossAxis = slotPositionsAndSizesCache.sizes
-
// with is needed because of the double receiver (Density, Arrangement).
with(crossAxisArrangement) {
arrange(
@@ -216,68 +230,73 @@ private fun SpannedGrid(
)
}
val startPositions = slotPositionsAndSizesCache.positions
-
val mainAxisSpacingPx = mainAxisSpacing.roundToPx()
val mainAxisTotalGaps = (totalMainAxisGroups - 1) * mainAxisSpacingPx
- val mainAxisSize = if (isVertical) constraints.maxHeight else constraints.maxWidth
+ val mainAxisMaxSize = if (isVertical) constraints.maxHeight else constraints.maxWidth
val mainAxisElementConstraint =
- if (mainAxisSize == Constraints.Infinity) {
+ if (mainAxisMaxSize == Constraints.Infinity) {
Constraints.Infinity
} else {
- max(0, (mainAxisSize - mainAxisTotalGaps) / totalMainAxisGroups)
+ max(0, (mainAxisMaxSize - mainAxisTotalGaps) / totalMainAxisGroups)
}
- val mainAxisSizes = IntArray(totalMainAxisGroups) { 0 }
-
- var currentSlot = 0
- var mainAxisGroup = 0
+ var mainAxisTotalSize = mainAxisTotalGaps
+ var currentMainAxis = 0
+ var currentMainAxisMax = 0
val placeables =
- measurables.mapIndexed { index, measurable ->
+ measurables.fastMapIndexed { index, measurable ->
val span = spans[index]
- if (currentSlot + span > primarySpaces) {
- currentSlot = 0
- mainAxisGroup += 1
- }
+ val position = positions[index]
val crossAxisConstraint =
- calculateWidth(cellSizesInCrossAxis, startPositions, currentSlot, span)
- PlaceResult(
- measurable.measure(
- makeConstraint(
- isVertical,
- mainAxisElementConstraint,
- crossAxisConstraint,
- )
- ),
- currentSlot,
- mainAxisGroup,
+ calculateWidth(cellSizesInCrossAxis, startPositions, position.second, span)
+
+ measurable
+ .measure(
+ makeConstraint(isVertical, mainAxisElementConstraint, crossAxisConstraint)
)
.also {
- currentSlot += span
- mainAxisSizes[mainAxisGroup] =
- max(
- mainAxisSizes[mainAxisGroup],
- if (isVertical) it.placeable.height else it.placeable.width,
- )
+ val placeableSize = if (isVertical) it.height else it.width
+ if (position.first != currentMainAxis) {
+ // New row -- Add the max size to the total and reset the max
+ mainAxisTotalSize += currentMainAxisMax
+ currentMainAxisMax = placeableSize
+ currentMainAxis = position.first
+ } else {
+ currentMainAxisMax = max(currentMainAxisMax, placeableSize)
+ }
}
}
+ mainAxisTotalSize += currentMainAxisMax
- val mainAxisTotalSize = mainAxisTotalGaps + mainAxisSizes.sum()
- val mainAxisStartingPoints =
- mainAxisSizes.runningFold(0) { acc, value -> acc + value + mainAxisSpacingPx }
val height = if (isVertical) mainAxisTotalSize else crossAxisSize
val width = if (isVertical) crossAxisSize else mainAxisTotalSize
layout(width, height) {
- placeables.forEach { (placeable, slot, mainAxisGroup) ->
+ var previousMainAxis = 0
+ var currentMainAxisPosition = 0
+ var currentMainAxisMax = 0
+ placeables.forEachIndexed { index, placeable ->
+ val slot = positions[index].second
+ val mainAxisSize = if (isVertical) placeable.height else placeable.width
+
+ if (positions[index].first != previousMainAxis) {
+ // Move up a row + padding
+ currentMainAxisPosition += currentMainAxisMax + mainAxisSpacingPx
+ currentMainAxisMax = mainAxisSize
+ previousMainAxis = positions[index].first
+ } else {
+ currentMainAxisMax = max(currentMainAxisMax, mainAxisSize)
+ }
+
val x =
if (isVertical) {
startPositions[slot]
} else {
- mainAxisStartingPoints[mainAxisGroup]
+ currentMainAxisPosition
}
val y =
if (isVertical) {
- mainAxisStartingPoints[mainAxisGroup]
+ currentMainAxisPosition
} else {
startPositions[slot]
}
@@ -321,9 +340,3 @@ private fun calculateCellsCrossAxisSize(
outArray[index] = slotSize + if (index < remainingPixels) 1 else 0
}
}
-
-private data class PlaceResult(
- val placeable: Placeable,
- val slotIndex: Int,
- val mainAxisGroup: Int,
-)
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/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/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 cc0efbcf3e44..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();
@@ -2489,6 +2490,7 @@ public class KeyguardViewMediator implements CoreStartable,
Log.e(TAG,
"doKeyguard: already showing, but re-showing because we're interactive or "
+ "were in the middle of hiding.");
+ notifyLockNowCallback();
}
}
@@ -2993,7 +2995,6 @@ public class KeyguardViewMediator implements CoreStartable,
startKeyguardTransition(showing, aodShowing);
} else {
try {
-
mActivityTaskManagerService.setLockScreenShown(showing, aodShowing);
} catch (RemoteException ignored) {
}
@@ -3649,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/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..07ed194dd68f 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
@@ -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/repository/KeyguardRepository.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardRepository.kt
index acb98ede3e80..affcd33b7170 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
@@ -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/domain/interactor/KeyguardClockInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardClockInteractor.kt
index 63cf4f72e415..ab0efed2cb76 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
@@ -85,7 +85,7 @@ constructor(
val previewClock: Flow<ClockController> = keyguardClockRepository.previewClock
- val clockEventController: ClockEventController by keyguardClockRepository::clockEventController
+ val clockEventController: ClockEventController = keyguardClockRepository.clockEventController
var clock: ClockController? by keyguardClockRepository.clockEventController::clock
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/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 def1ac8742da..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
@@ -81,22 +82,92 @@ object KeyguardSmartspaceViewBinder {
}
if (com.android.systemui.shared.Flags.clockReactiveSmartspaceLayout()) {
+ val xBuffer =
+ keyguardRootView.context.resources.getDimensionPixelSize(
+ R.dimen.smartspace_padding_horizontal
+ )
+ val yBuffer =
+ keyguardRootView.context.resources.getDimensionPixelSize(
+ R.dimen.smartspace_padding_vertical
+ )
+
+ val smallViewIds =
+ listOf(sharedR.id.date_smartspace_view, sharedR.id.weather_smartspace_view)
+
+ val largeViewIds =
+ listOf(
+ sharedR.id.date_smartspace_view_large,
+ sharedR.id.weather_smartspace_view_large,
+ )
+
launch("$TAG#smartspaceViewModel.burnInLayerVisibility") {
- keyguardRootViewModel.burnInLayerVisibility.collect { visibility ->
- if (clockViewModel.isLargeClockVisible.value) {
- // hide small clock date/weather
- val dateView =
- keyguardRootView.requireViewById<View>(
- sharedR.id.date_smartspace_view
- )
- dateView.visibility = View.GONE
- val weatherView =
- keyguardRootView.requireViewById<View>(
- sharedR.id.weather_smartspace_view
- )
- weatherView.visibility = View.GONE
+ combine(
+ keyguardRootViewModel.burnInLayerVisibility,
+ clockViewModel.isLargeClockVisible,
+ ::Pair,
+ )
+ .collect { (visibility, isLargeClock) ->
+ if (isLargeClock) {
+ // hide small clock date/weather
+ for (viewId in smallViewIds) {
+ keyguardRootView.findViewById<View>(viewId)?.let {
+ it.visibility = View.GONE
+ }
+ }
+ }
+ }
+ }
+
+ launch("$TAG#clockEventController.onClockBoundsChanged") {
+ // Whenever the doze amount changes, the clock may update it's view bounds.
+ // We need to update our layout position as a result. We could do this via
+ // `requestLayout`, but that's quite expensive when enclosed in since this
+ // recomputes the entire ConstraintLayout, so instead we do it manually. We
+ // would use translationX/Y for this, but that's used by burnin.
+ combine(
+ clockViewModel.isLargeClockVisible,
+ clockViewModel.clockEventController.onClockBoundsChanged,
+ ::Pair,
+ )
+ .collect { (isLargeClock, clockBounds) ->
+ for (id in (if (isLargeClock) smallViewIds else largeViewIds)) {
+ keyguardRootView.findViewById<View>(id)?.let {
+ it.visibility = View.GONE
+ }
+ }
+
+ if (clockBounds == VRectF.ZERO) return@collect
+ if (isLargeClock) {
+ val largeDateHeight =
+ keyguardRootView
+ .findViewById<View>(
+ sharedR.id.date_smartspace_view_large
+ )
+ ?.height ?: 0
+ for (id in largeViewIds) {
+ keyguardRootView.findViewById<View>(id)?.let { view ->
+ val viewHeight = view.height
+ val offset = (largeDateHeight - viewHeight) / 2
+ view.top =
+ (clockBounds.bottom + yBuffer + offset).toInt()
+ view.bottom = view.top + viewHeight
+ }
+ }
+ } else {
+ for (id in smallViewIds) {
+ keyguardRootView.findViewById<View>(id)?.let { view ->
+ val viewWidth = view.width
+ if (view.isLayoutRtl()) {
+ view.right = (clockBounds.left - xBuffer).toInt()
+ view.left = view.right - viewWidth
+ } else {
+ view.left = (clockBounds.right + xBuffer).toInt()
+ view.right = view.left + viewWidth
+ }
+ }
+ }
+ }
}
- }
}
}
}
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/view/layout/sections/SmartspaceSection.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/SmartspaceSection.kt
index 37cc852ffe00..d0b5f743c277 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/SmartspaceSection.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/SmartspaceSection.kt
@@ -226,7 +226,7 @@ constructor(
ConstraintSet.TOP,
customR.id.lockscreen_clock_view_large,
ConstraintSet.BOTTOM,
- dateWeatherPaddingStart,
+ context.resources.getDimensionPixelSize(R.dimen.smartspace_padding_vertical),
)
connect(
@@ -291,7 +291,9 @@ constructor(
ConstraintSet.START,
customR.id.lockscreen_clock_view,
ConstraintSet.END,
- 20,
+ context.resources.getDimensionPixelSize(
+ R.dimen.smartspace_padding_horizontal
+ ),
)
connect(
sharedR.id.date_smartspace_view,
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/GlanceableHubToPrimaryBouncerTransitionViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/GlanceableHubToPrimaryBouncerTransitionViewModel.kt
index 40010548a268..c088900f9304 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/GlanceableHubToPrimaryBouncerTransitionViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/GlanceableHubToPrimaryBouncerTransitionViewModel.kt
@@ -17,33 +17,39 @@
package com.android.systemui.keyguard.ui.viewmodel
import com.android.systemui.Flags
+import com.android.systemui.communal.domain.interactor.CommunalSceneInteractor
import com.android.systemui.communal.domain.interactor.CommunalSettingsInteractor
import com.android.systemui.communal.shared.model.CommunalBackgroundType
import com.android.systemui.dagger.SysUISingleton
-import com.android.systemui.keyguard.domain.interactor.FromLockscreenTransitionInteractor
+import com.android.systemui.keyguard.domain.interactor.FromGlanceableHubTransitionInteractor
import com.android.systemui.keyguard.shared.model.Edge
import com.android.systemui.keyguard.shared.model.KeyguardState.GLANCEABLE_HUB
import com.android.systemui.keyguard.shared.model.KeyguardState.PRIMARY_BOUNCER
import com.android.systemui.keyguard.ui.KeyguardTransitionAnimationFlow
import com.android.systemui.keyguard.ui.transitions.BlurConfig
import com.android.systemui.keyguard.ui.transitions.PrimaryBouncerTransition
+import com.android.systemui.statusbar.policy.KeyguardStateController
import javax.inject.Inject
+import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.filter
import kotlinx.coroutines.flow.flatMapLatest
+@OptIn(ExperimentalCoroutinesApi::class)
@SysUISingleton
class GlanceableHubToPrimaryBouncerTransitionViewModel
@Inject
constructor(
private val blurConfig: BlurConfig,
animationFlow: KeyguardTransitionAnimationFlow,
- communalSettingsInteractor: CommunalSettingsInteractor,
+ private val communalSettingsInteractor: CommunalSettingsInteractor,
+ private val communalSceneInteractor: CommunalSceneInteractor,
+ private val keyguardStateController: KeyguardStateController,
) : PrimaryBouncerTransition {
private val transitionAnimation =
animationFlow
.setup(
- duration = FromLockscreenTransitionInteractor.TO_PRIMARY_BOUNCER_DURATION,
+ duration = FromGlanceableHubTransitionInteractor.TO_BOUNCER_DURATION,
edge = Edge.INVALID,
)
.setupWithoutSceneContainer(edge = Edge.create(GLANCEABLE_HUB, PRIMARY_BOUNCER))
@@ -59,6 +65,13 @@ constructor(
transitionAnimation.immediatelyTransitionTo(blurConfig.maxBlurRadiusPx)
}
+ /** Whether to delay the animation to fade in bouncer elements. */
+ fun willDelayAppearAnimation(isLandscape: Boolean): Boolean =
+ communalSettingsInteractor.isV2FlagEnabled() &&
+ communalSceneInteractor.isIdleOnCommunal.value &&
+ !keyguardStateController.isKeyguardScreenRotationAllowed() &&
+ isLandscape
+
override val notificationBlurRadius: Flow<Float> =
transitionAnimation.immediatelyTransitionTo(0.0f)
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardClockViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardClockViewModel.kt
index cf5cc264be8d..dcbf7b5a9335 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardClockViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardClockViewModel.kt
@@ -19,6 +19,7 @@ package com.android.systemui.keyguard.ui.viewmodel
import android.content.Context
import android.content.res.Resources
import androidx.constraintlayout.helper.widget.Layer
+import com.android.keyguard.ClockEventController
import com.android.systemui.common.ui.domain.interactor.ConfigurationInteractor
import com.android.systemui.customization.R as customR
import com.android.systemui.dagger.SysUISingleton
@@ -68,6 +69,7 @@ constructor(
initialValue = true,
)
+ val clockEventController: ClockEventController = keyguardClockInteractor.clockEventController
val currentClock = keyguardClockInteractor.currentClock
val hasCustomWeatherDataDisplay =
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..6d8a943d3e28 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
@@ -132,6 +132,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,
@@ -288,6 +290,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/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/log/table/Diffable.kt b/packages/SystemUI/src/com/android/systemui/log/table/Diffable.kt
index 9fddbfb16d4d..a1546949835c 100644
--- a/packages/SystemUI/src/com/android/systemui/log/table/Diffable.kt
+++ b/packages/SystemUI/src/com/android/systemui/log/table/Diffable.kt
@@ -16,6 +16,11 @@
package com.android.systemui.log.table
+import com.android.systemui.kairos.BuildScope
+import com.android.systemui.kairos.ExperimentalKairosApi
+import com.android.systemui.kairos.State
+import com.android.systemui.kairos.changes
+import com.android.systemui.kairos.effect
import com.android.systemui.util.kotlin.pairwiseBy
import kotlinx.coroutines.flow.Flow
@@ -184,3 +189,94 @@ fun <T> Flow<List<T>>.logDiffsForTable(
newVal
}
}
+
+/** See [logDiffsForTable(TableLogBuffer, String, T)]. */
+@ExperimentalKairosApi
+@JvmName("logIntDiffsForTable")
+fun BuildScope.logDiffsForTable(
+ intState: State<Int?>,
+ tableLogBuffer: TableLogBuffer,
+ columnPrefix: String = "",
+ columnName: String,
+) {
+ var isInitial = true
+ intState.observe { new ->
+ tableLogBuffer.logChange(columnPrefix, columnName, new, isInitial = isInitial)
+ isInitial = false
+ }
+}
+
+/**
+ * Each time the flow is updated with a new value, logs the differences between the previous value
+ * and the new value to the given [tableLogBuffer].
+ *
+ * The new value's [Diffable.logDiffs] method will be used to log the differences to the table.
+ *
+ * @param columnPrefix a prefix that will be applied to every column name that gets logged.
+ */
+@ExperimentalKairosApi
+fun <T : Diffable<T>> BuildScope.logDiffsForTable(
+ diffableState: State<T>,
+ tableLogBuffer: TableLogBuffer,
+ columnPrefix: String = "",
+) {
+ val initialValue = diffableState.sampleDeferred()
+ effect {
+ // Fully log the initial value to the table.
+ tableLogBuffer.logChange(columnPrefix, isInitial = true) { row ->
+ initialValue.value.logFull(row)
+ }
+ }
+ diffableState.changes.observe { newState ->
+ val prevState = diffableState.sample()
+ tableLogBuffer.logDiffs(columnPrefix, prevVal = prevState, newVal = newState)
+ }
+}
+
+/** See [logDiffsForTable(TableLogBuffer, String, T)]. */
+@ExperimentalKairosApi
+@JvmName("logBooleanDiffsForTable")
+fun BuildScope.logDiffsForTable(
+ booleanState: State<Boolean>,
+ tableLogBuffer: TableLogBuffer,
+ columnPrefix: String = "",
+ columnName: String,
+) {
+ var isInitial = true
+ booleanState.observe { new ->
+ tableLogBuffer.logChange(columnPrefix, columnName, new, isInitial = isInitial)
+ isInitial = false
+ }
+}
+
+/** See [logDiffsForTable(TableLogBuffer, String, T)]. */
+@ExperimentalKairosApi
+@JvmName("logStringDiffsForTable")
+fun BuildScope.logDiffsForTable(
+ stringState: State<String?>,
+ tableLogBuffer: TableLogBuffer,
+ columnPrefix: String = "",
+ columnName: String,
+) {
+ var isInitial = true
+ stringState.observe { new ->
+ tableLogBuffer.logChange(columnPrefix, columnName, new, isInitial = isInitial)
+ isInitial = false
+ }
+}
+
+/** See [logDiffsForTable(TableLogBuffer, String, T)]. */
+@ExperimentalKairosApi
+@JvmName("logListDiffsForTable")
+fun <T> BuildScope.logDiffsForTable(
+ listState: State<List<T>>,
+ tableLogBuffer: TableLogBuffer,
+ columnPrefix: String = "",
+ columnName: String,
+) {
+ var isInitial = true
+ listState.observe { new ->
+ tableLogBuffer.logChange(columnPrefix, columnName, new.toString(), isInitial = isInitial)
+ isInitial = false
+ }
+}
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/controls/domain/pipeline/LegacyMediaDataManagerImpl.kt b/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/LegacyMediaDataManagerImpl.kt
index 46cf0a63e93d..309b6751176c 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
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..dbd2250a75b0 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
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..94df4b353c94 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
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/controller/MediaCarouselController.kt b/packages/SystemUI/src/com/android/systemui/media/controls/ui/controller/MediaCarouselController.kt
index 7b1ae57ed421..ac6343c6bb64 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
@@ -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)
}
}
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 9b443f56636a..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,41 +64,20 @@ 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" })
- fun logMediaHostVisibility(location: Int, visible: Boolean) {
+ fun logMediaHostVisibility(location: Int, visible: Boolean, oldState: Boolean) {
buffer.log(
TAG,
LogLevel.DEBUG,
{
int1 = location
bool1 = visible
+ bool2 = oldState
},
- { "media host visibility changed location=$location, visible:$visible" },
+ { "media host visibility changed location=$location, visible:$visible, was:$oldState" },
)
}
}
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..006eb203a669 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;
@@ -310,7 +281,6 @@ public class MediaControlPanel {
MediaOutputDialogManager mediaOutputDialogManager,
MediaCarouselController mediaCarouselController,
FalsingManager falsingManager,
- SystemClock systemClock,
MediaUiEventLogger logger,
KeyguardStateController keyguardStateController,
ActivityIntentHelper activityIntentHelper,
@@ -330,7 +300,6 @@ public class MediaControlPanel {
mMediaOutputDialogManager = mediaOutputDialogManager;
mMediaCarouselController = mediaCarouselController;
mFalsingManager = falsingManager;
- mSystemClock = systemClock;
mLogger = logger;
mKeyguardStateController = keyguardStateController;
mActivityIntentHelper = activityIntentHelper;
@@ -373,16 +342,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
@@ -465,7 +424,7 @@ public class MediaControlPanel {
mSeekBarViewModel.attachTouchHandlers(vh.getSeekBar());
mSeekBarViewModel.setScrubbingChangeListener(mScrubbingChangeListener);
mSeekBarViewModel.setEnabledChangeListener(mEnabledChangeListener);
- mMediaViewController.attach(player, MediaViewController.TYPE.PLAYER);
+ mMediaViewController.attach(player);
vh.getPlayer().setOnLongClickListener(v -> {
if (mFalsingManager.isFalseLongTap(FalsingManager.LOW_PENALTY)) return true;
@@ -522,26 +481,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 +807,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 +914,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 +953,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 +1315,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 +1370,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 +1384,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 +1412,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 +1463,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 c6894082f1c8..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) {
@@ -1129,10 +1139,11 @@ constructor(
traceSection("MediaHierarchyManager#updateHostAttachment") {
if (SceneContainerFlag.isEnabled) {
// No need to manage transition states - just update the desired location directly
- logger.logMediaHostAttachment(desiredLocation)
+ val host = getHost(desiredLocation)
+ logger.logMediaHostAttachment(desiredLocation, host?.visible)
mediaCarouselController.onDesiredLocationChanged(
desiredLocation = desiredLocation,
- desiredHostState = getHost(desiredLocation),
+ desiredHostState = host,
animate = false,
)
return
@@ -1169,7 +1180,8 @@ constructor(
// that and directly set the mediaFrame's bounds within the premeasured host.
targetHost.addView(mediaFrame)
}
- logger.logMediaHostAttachment(currentAttachmentLocation)
+ val host = getHost(currentAttachmentLocation)
+ logger.logMediaHostAttachment(currentAttachmentLocation, host?.visible)
if (isCrossFadeAnimatorRunning) {
// When cross-fading with an animation, we only notify the media carousel of the
// location change, once the view is reattached to the new place and not
@@ -1313,6 +1325,7 @@ constructor(
isHomeScreenShadeVisibleToUser() ||
isGlanceableHubVisibleToUser()
val mediaVisible = qsExpanded || hasActiveMediaOrRecommendation
+ logger.logUserVisibilityChange(shadeVisible, mediaVisible)
mediaCarouselController.mediaCarouselScrollHandler.visibleToUser =
shadeVisible && mediaVisible
}
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..dba190022c8b 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
@@ -417,13 +404,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 +414,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 +446,6 @@ constructor(
squishedViewState.widgetStates.get(id)?.let { state -> state.height = squishedHeight }
}
- // media player
calculateWidgetGroupAlphaForSquishiness(
MediaViewHolder.expandedBottomActionIds,
squishedViewState.measureHeight.toFloat(),
@@ -480,20 +458,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 +625,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) {
@@ -691,7 +655,7 @@ constructor(
seekBarViewModel.setEnabledChangeListener(enabledChangeListener)
val mediaCard = mediaViewHolder.player
- attach(mediaViewHolder.player, TYPE.PLAYER)
+ attach(mediaViewHolder.player)
val turbulenceNoiseView = mediaViewHolder.turbulenceNoiseView
turbulenceNoiseController = TurbulenceNoiseController(turbulenceNoiseView)
@@ -813,15 +777,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 +981,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/controller/MediaViewLogger.kt b/packages/SystemUI/src/com/android/systemui/media/controls/ui/controller/MediaViewLogger.kt
index 1514db38d882..089d16b98de8 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/ui/controller/MediaViewLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/ui/controller/MediaViewLogger.kt
@@ -36,7 +36,7 @@ class MediaViewLogger @Inject constructor(@MediaViewLog private val buffer: LogB
int1 = width
int2 = height
},
- { "size ($str1): $int1 x $int2" }
+ { "size ($str1): $int1 x $int2" },
)
}
@@ -49,11 +49,31 @@ class MediaViewLogger @Inject constructor(@MediaViewLog private val buffer: LogB
int1 = startLocation
int2 = endLocation
},
- { "location ($str1): $int1 -> $int2" }
+ { "location ($str1): $int1 -> $int2" },
)
}
- fun logMediaHostAttachment(host: Int) {
- buffer.log(TAG, LogLevel.DEBUG, { int1 = host }, { "Host (updateHostAttachment): $int1" })
+ fun logMediaHostAttachment(host: Int, visible: Boolean?) {
+ buffer.log(
+ TAG,
+ LogLevel.DEBUG,
+ {
+ int1 = host
+ str1 = visible.toString()
+ },
+ { "Host (updateHostAttachment): $int1 visible $str1" },
+ )
+ }
+
+ fun logUserVisibilityChange(shadeVisible: Boolean, mediaVisible: Boolean) {
+ buffer.log(
+ TAG,
+ LogLevel.DEBUG,
+ {
+ bool1 = shadeVisible
+ bool2 = mediaVisible
+ },
+ { "User visibility shade: $shadeVisible media: $mediaVisible" },
+ )
}
}
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/MediaHost.kt b/packages/SystemUI/src/com/android/systemui/media/controls/ui/view/MediaHost.kt
index 11251cdb6315..a518349ea424 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
@@ -208,6 +208,7 @@ class MediaHost(
* the visibility has changed
*/
fun updateViewVisibility() {
+ val oldState = state.visible
state.visible =
if (mediaCarouselController.isLockedAndHidden()) {
false
@@ -217,9 +218,9 @@ class MediaHost(
mediaDataManager.hasAnyMediaOrRecommendation()
}
val newVisibility = if (visible) View.VISIBLE else View.GONE
- if (newVisibility != hostView.visibility) {
+ if (oldState != state.visible || newVisibility != hostView.visibility) {
hostView.visibility = newVisibility
- debugLogger.logMediaHostVisibility(location, visible)
+ debugLogger.logMediaHostVisibility(location, visible, oldState)
visibleChangedListeners.forEach { it.invoke(visible) }
}
}
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/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/SeekBarViewModel.kt b/packages/SystemUI/src/com/android/systemui/media/controls/ui/viewmodel/SeekBarViewModel.kt
index a1f0cc33c065..8744c5c9a838 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
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 f8e57ef489aa..795e811db2bc 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,13 +63,21 @@ 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);
+ mMainExecutor = mainExecutor;
+ mBackgroundExecutor = backgroundExecutor;
mIsInitVolumeFirstTime = true;
}
@@ -181,27 +191,26 @@ 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);
- mSeekBar.setProgressTintList(
- ColorStateList.valueOf(mController.getColorSeekbarProgress()));
+ updateSeekBarProgressColor();
updateContainerContentA11yImportance(true /* isImportant */);
renderItem(mediaItem, position);
}
/** 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);
}
@@ -298,8 +307,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);
}
@@ -319,8 +328,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,6 +341,16 @@ public class MediaOutputAdapterLegacy extends MediaOutputAdapterBase {
}
}
+ private void updateSeekBarProgressColor() {
+ mSeekBar.setProgressTintList(ColorStateList.valueOf(
+ mController.getColorSchemeLegacy().getColorSeekbarProgress()));
+ final Drawable contrastDotDrawable =
+ ((LayerDrawable) mSeekBar.getProgressDrawable()).findDrawableByLayerId(
+ R.id.contrast_dot);
+ contrastDotDrawable.setTintList(ColorStateList.valueOf(
+ mController.getColorSchemeLegacy().getColorItemContent()));
+ }
+
void updateSeekbarProgressBackground() {
final ClipDrawable clipDrawable =
(ClipDrawable) ((LayerDrawable) mSeekBar.getProgressDrawable())
@@ -494,9 +513,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
@@ -525,8 +545,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();
}
@@ -576,9 +596,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);
@@ -591,8 +612,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));
@@ -602,7 +624,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) {
@@ -705,15 +727,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()));
});
});
}
@@ -798,7 +820,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..9e6fa48d6f98 100644
--- a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaSwitchingController.java
+++ b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaSwitchingController.java
@@ -83,6 +83,8 @@ 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.
@@ -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,
@@ -230,20 +228,7 @@ public class MediaSwitchingController
mMetricLogger = new MediaOutputMetricLogger(mContext, mPackageName);
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);
@@ -568,26 +553,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,34 +572,6 @@ 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);
@@ -648,10 +594,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 +602,12 @@ public class MediaSwitchingController
return categorizeMediaItemsLocked(
/* connectedMediaDevice */ null,
devices,
- selectedDevicesIds,
needToHandleMutingExpectedDevice);
} else {
// selected device exist
return categorizeMediaItemsLocked(
connectedMediaDevice,
devices,
- selectedDevicesIds,
/* needToHandleMutingExpectedDevice */ false);
}
}
@@ -701,18 +641,28 @@ public class MediaSwitchingController
devices.removeAll(targetMediaDevices);
targetMediaDevices.addAll(devices);
}
- List<MediaItem> finalMediaItems = new ArrayList<>();
+ List<MediaItem> finalMediaItems = targetMediaDevices.stream()
+ .map(MediaItem::createDeviceMediaItem)
+ .collect(Collectors.toList());
+
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));
+
+ 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);
@@ -741,9 +691,11 @@ public class MediaSwitchingController
@GuardedBy("mMediaDevicesLock")
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());
}
@@ -1091,7 +1043,7 @@ public class MediaSwitchingController
mVolumePanelGlobalStateInteractor,
mUserTracker);
MediaOutputBroadcastDialog dialog = new MediaOutputBroadcastDialog(mContext, true,
- broadcastSender, controller);
+ broadcastSender, controller, mMainExecutor, mBackgroundExecutor);
dialog.show();
}
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..fea5b3267562
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/media/remedia/ui/compose/DismissibleHorizontalPager.kt
@@ -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 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,
+ 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,
+ 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..9eb55a8eff2e
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/media/remedia/ui/compose/Media.kt
@@ -0,0 +1,1152 @@
+/*
+ * 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.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.mutableStateListOf
+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.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.pointerInput
+import androidx.compose.ui.layout.ContentScale
+import androidx.compose.ui.layout.Layout
+import androidx.compose.ui.platform.LocalContext
+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 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
+
+/**
+ * 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
+ }
+
+ 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
+ },
+ )
+ }
+ },
+ modifier = modifier.padding(8.dp).clip(roundedCornerShape),
+ ) { 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),
+ )
+
+ Row(
+ horizontalArrangement = Arrangement.spacedBy(8.dp),
+ modifier = Modifier.align(Alignment.TopEnd),
+ ) {
+ viewModel.outputSwitcherChips.fastForEach { chip ->
+ OutputSwitcherChip(viewModel = chip, colorScheme = colorScheme)
+ }
+ }
+ }
+
+ // 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)) {
+ Slider(
+ interactionSource = interactionSource,
+ value = viewModel.progress,
+ onValueChange = { progress -> viewModel.onScrubChange(progress) },
+ onValueChangeFinished = { viewModel.onScrubFinished() },
+ colors = colors,
+ thumb = {
+ SeekBarThumb(interactionSource = interactionSource, colors = colors)
+ },
+ track = { sliderState ->
+ SeekBarTrack(
+ sliderState = sliderState,
+ isSquiggly = viewModel.isSquiggly,
+ colors = colors,
+ modifier = Modifier.fillMaxWidth(),
+ )
+ },
+ modifier = Modifier.fillMaxWidth(),
+ )
+ }
+ }
+
+ 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,
+ )
+ }
+ }
+ }
+}
+
+/** 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,
+ val isFalsingProtectionNeeded: Boolean = false,
+)
+
+@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..c34c7337290f
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/media/remedia/ui/viewmodel/MediaNavigationViewModel.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.media.remedia.ui.viewmodel
+
+import androidx.annotation.FloatRange
+
+/**
+ * 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: () -> Unit,
+ ) : 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..b4f3d2724e75
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/media/remedia/ui/viewmodel/MediaViewModel.kt
@@ -0,0 +1,253 @@
+/*
+ * 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 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.graphics.ImageBitmap
+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.res.R
+import dagger.assisted.Assisted
+import dagger.assisted.AssistedFactory
+import dagger.assisted.AssistedInject
+import kotlin.math.roundToLong
+import kotlinx.coroutines.awaitCancellation
+
+/** Models UI state for a media element. */
+class MediaViewModel
+@AssistedInject
+constructor(
+ private val interactor: MediaInteractor,
+ @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 = {
+ interactor.seek(
+ sessionKey = session.key,
+ to = (seekProgress * session.durationMs).roundToLong(),
+ )
+ isScrubbing = false
+ },
+ )
+ } 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 = {
+ interactor.hide(session.key)
+ isGutsVisible = false
+ },
+ )
+ } else {
+ MediaGutsButtonViewModel(
+ text = context.getString(R.string.cancel),
+ onClick = { isGutsVisible = false },
+ )
+ },
+ secondaryAction =
+ MediaGutsButtonViewModel(
+ text = context.getString(R.string.cancel),
+ onClick = { 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 = { interactor.openMediaSettings() },
+ ),
+ onLongClick = { isGutsVisible = false },
+ )
+ }
+
+ override val outputSwitcherChips: List<MediaOutputSwitcherChipViewModel>
+ get() {
+ return listOf(
+ MediaOutputSwitcherChipViewModel(
+ icon = session.outputDevice.icon,
+ text = session.outputDevice.name,
+ onClick = {
+ // 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 = {
+ // TODO(b/397989775): tell the UI to show the output switcher.
+ },
+ )
+ }
+
+ override val onClick = 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 ?: {},
+ )
+ is MediaActionModel.None,
+ is MediaActionModel.ReserveSpace -> null
+ }
+ }
+
+ private fun MediaActionModel.toSecondaryActionViewModel(): MediaSecondaryActionViewModel {
+ return when (this) {
+ is MediaActionModel.Action ->
+ MediaSecondaryActionViewModel.Action(icon = icon, onClick = onClick)
+ is MediaActionModel.ReserveSpace -> MediaSecondaryActionViewModel.ReserveSpace
+ is MediaActionModel.None -> MediaSecondaryActionViewModel.None
+ }
+ }
+
+ @AssistedFactory
+ interface Factory {
+ fun create(context: Context, carouselVisibility: MediaCarouselVisibility): MediaViewModel
+ }
+}
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..aaed606f8fb2
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/model/SysUIStateChange.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.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
+ }
+
+ fun hasChanges() = flagsToSet != 0L || flagsToClear != 0L
+
+ /**
+ * 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) }
+ }
+
+ 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..e99ee7ddb919 100644
--- a/packages/SystemUI/src/com/android/systemui/model/SysUiState.kt
+++ b/packages/SystemUI/src/com/android/systemui/model/SysUiState.kt
@@ -16,64 +16,125 @@
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.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 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 _flags: Long = 0
private var flagsToSet: Long = 0
private var flagsToClear: Long = 0
/**
* 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
- }
+ override fun setFlag(@SystemUiStateFlags flag: Long, enabled: Boolean): SysUiState {
+ val toSet = flagWithOptionalOverrides(flag, enabled, displayId, sceneContainerPlugin)
- if (enabled) {
+ if (toSet) {
flagsToSet = flagsToSet or flag
} else {
flagsToClear = flagsToClear or flag
@@ -81,20 +142,22 @@ class SysUiState(
return this
}
- /** Call to save all the flags updated from [.setFlag]. */
- fun commitUpdate(displayId: Int) {
- updateFlags(displayId)
+ @Deprecated(
+ "Each SysUIState instance is now display specific. Just use commitUpdate.",
+ ReplaceWith("commitUpdate()"),
+ )
+ override fun commitUpdate(displayId: Int) {
+ // TODO b/398011576 - handle updates for different displays.
+ commitUpdate()
+ }
+
+ override fun commitUpdate() {
+ updateFlags()
flagsToSet = 0
flagsToClear = 0
}
- 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
- }
-
+ private fun updateFlags() {
var newState = flags
newState = newState or flagsToSet
newState = newState and flagsToClear.inv()
@@ -103,15 +166,12 @@ class SysUiState(
/** Notify all those who are registered that the state has changed. */
private fun notifyAndSetSystemUiStateChanged(newFlags: Long, oldFlags: Long) {
- if (DEBUG) {
+ if (SysUiState.DEBUG) {
Log.d(TAG, "SysUiState changed: old=$oldFlags new=$newFlags")
}
if (newFlags != oldFlags) {
- callbacks.forEach { callback: SysUiStateCallback ->
- callback.onSystemUiStateChanged(newFlags)
- }
-
- flags = newFlags
+ _flags = newFlags
+ stateDispatcher.dispatchSysUIStateChange(newFlags, displayId)
}
}
@@ -127,14 +187,62 @@ class SysUiState(
pw.println(QuickStepContract.isAssistantGestureDisabled(flags))
}
- /** 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/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 44c8dc38ff8c..8920c86282da 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/composefragment/QSFragmentCompose.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/composefragment/QSFragmentCompose.kt
@@ -18,6 +18,7 @@ package com.android.systemui.qs.composefragment
import android.annotation.SuppressLint
import android.content.Context
+import android.content.res.Configuration
import android.graphics.PointF
import android.graphics.Rect
import android.os.Bundle
@@ -42,13 +43,13 @@ 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.offset
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.requiredHeightIn
import androidx.compose.foundation.verticalScroll
import androidx.compose.material3.MaterialTheme
import androidx.compose.runtime.Composable
+import androidx.compose.runtime.CompositionLocalProvider
import androidx.compose.runtime.DisposableEffect
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.getValue
@@ -59,16 +60,20 @@ 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
import androidx.compose.ui.layout.positionOnScreen
import androidx.compose.ui.platform.ComposeView
+import androidx.compose.ui.platform.LocalConfiguration
+import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.dimensionResource
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.semantics.CustomAccessibilityAction
@@ -102,6 +107,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
@@ -128,6 +134,7 @@ import com.android.systemui.qs.panels.ui.compose.QuickQuickSettings
import com.android.systemui.qs.panels.ui.compose.TileGrid
import com.android.systemui.qs.shared.ui.ElementKeys
import com.android.systemui.qs.ui.composable.QuickSettingsShade
+import com.android.systemui.qs.ui.composable.QuickSettingsShade.systemGestureExclusionInShade
import com.android.systemui.qs.ui.composable.QuickSettingsTheme
import com.android.systemui.res.R
import com.android.systemui.util.LifecycleFragment
@@ -248,12 +255,25 @@ constructor(
@Composable
private fun Content() {
- PlatformTheme(isDarkTheme = true) {
+ PlatformTheme(isDarkTheme = true /* Delete AlwaysDarkMode when removing this */) {
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 +351,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.
@@ -626,7 +646,20 @@ 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)
+ },
+ )
}
val Media =
@Composable {
@@ -713,14 +746,26 @@ constructor(
)
val BrightnessSlider =
@Composable {
- BrightnessSliderContainer(
- viewModel = containerViewModel.brightnessSliderViewModel,
- modifier =
- Modifier.fillMaxWidth()
- .height(
- QuickSettingsShade.Dimensions.BrightnessSliderHeight
- ),
- )
+ AlwaysDarkMode {
+ 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 {
@@ -729,6 +774,18 @@ 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)
+ },
)
}
}
@@ -833,6 +890,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) }
}
@@ -1180,3 +1238,31 @@ 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()
+
+/**
+ * Forces the configuration and themes to be dark theme. This is needed in order to have
+ * [colorResource] retrieve the dark mode colors.
+ *
+ * This should be removed when we remove the force dark mode in [PlatformTheme] at the root of the
+ * compose hierarchy.
+ */
+@Composable
+private fun AlwaysDarkMode(content: @Composable () -> Unit) {
+ val currentConfig = LocalConfiguration.current
+ val darkConfig =
+ Configuration(currentConfig).apply {
+ uiMode =
+ (uiMode and (Configuration.UI_MODE_NIGHT_MASK.inv())) or
+ Configuration.UI_MODE_NIGHT_YES
+ }
+ val newContext = LocalContext.current.createConfigurationContext(darkConfig)
+ CompositionLocalProvider(
+ LocalConfiguration provides darkConfig,
+ LocalContext provides newContext,
+ ) {
+ content()
+ }
+}
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/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/BounceableInfo.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/BounceableInfo.kt
index eb6f97942ec0..b80e6b403cf3 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/BounceableInfo.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/BounceableInfo.kt
@@ -35,11 +35,16 @@ fun List<BounceableTileViewModel>.bounceableInfo(
index: Int,
column: Int,
columns: Int,
+ isFirstInRow: Boolean,
+ isLastInRow: Boolean,
): BounceableInfo {
- // Only look for neighbor bounceables if they are on the same row
+ // A tile may be the last in the row without being on the last column
val onLastColumn = sizedTile.onLastColumn(column, columns)
- val previousTile = getOrNull(index - 1)?.takeIf { column != 0 }
- val nextTile = getOrNull(index + 1)?.takeIf { !onLastColumn }
+
+ // Only look for neighbor bounceables if they are on the same row
+ val previousTile = getOrNull(index - 1)?.takeIf { !isFirstInRow }
+ val nextTile = getOrNull(index + 1)?.takeIf { !isLastInRow }
+
return BounceableInfo(this[index], previousTile, nextTile, !onLastColumn)
}
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..865ae9a37d87 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)
@@ -122,7 +121,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 495870f0a978..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,13 +51,7 @@ 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
- var cellIndex = 0
Box(modifier = modifier) {
GridAnchor()
VerticalSpannedGrid(
@@ -67,17 +61,23 @@ fun ContentScope.QuickQuickSettings(
spans = spans,
modifier = Modifier.sysuiResTag("qqs_tile_layout"),
keys = { sizedTiles[it].tile.spec },
- ) { spanIndex ->
+ ) { spanIndex, column, isFirstInColumn, isLastInColumn ->
val it = sizedTiles[spanIndex]
- val column = cellIndex % columns
- cellIndex += it.width
Element(it.tile.spec.toElementKey(spanIndex), Modifier) {
Tile(
tile = it.tile,
iconOnly = it.isIcon,
squishiness = { squishiness },
coroutineScope = scope,
- bounceableInfo = bounceables.bounceableInfo(it, spanIndex, column, columns),
+ bounceableInfo =
+ bounceables.bounceableInfo(
+ it,
+ index = spanIndex,
+ column = column,
+ columns = columns,
+ isFirstInRow = isFirstInColumn,
+ isLastInRow = isLastInColumn,
+ ),
tileHapticsViewModelFactoryProvider =
viewModel.tileHapticsViewModelFactoryProvider,
// There should be no QuickQuickSettings when the details view is enabled.
@@ -86,4 +86,6 @@ fun ContentScope.QuickQuickSettings(
}
}
}
+
+ TileListener(tiles, listening)
}
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..69b967a68c3c 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
@@ -78,6 +79,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
@@ -140,6 +142,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
@@ -290,8 +293,19 @@ fun DefaultEditTileGrid(
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,
@@ -444,7 +458,7 @@ private fun CurrentTilesGrid(
@Composable
private fun AvailableTileGrid(
- tiles: List<SizedTile<EditTileViewModel>>,
+ tiles: List<AvailableTileGridCell>,
selectionState: MutableSelectionState,
columns: Int,
onAddTile: (TileSpec) -> Unit,
@@ -453,7 +467,7 @@ 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()
@@ -478,11 +492,10 @@ private fun AvailableTileGrid(
horizontalArrangement = spacedBy(TileArrangementPadding),
modifier = Modifier.fillMaxWidth().height(IntrinsicSize.Max),
) {
- row.forEachIndexed { index, tileGridCell ->
- key(tileGridCell.tile.tileSpec) {
+ row.forEach { tileGridCell ->
+ key(tileGridCell.key) {
AvailableTileGridCell(
cell = tileGridCell,
- index = index,
dragAndDropState = dragAndDropState,
selectionState = selectionState,
onAddTile = onAddTile,
@@ -505,10 +518,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 +697,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 +746,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(
@@ -796,7 +813,7 @@ fun EditTile(
placeable.place(startPadding.roundToInt(), 0)
}
}
- .tilePadding(),
+ .largeTilePadding(),
) {
// Icon
Box(Modifier.size(ToggleTargetSize)) {
@@ -819,9 +836,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 {
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 dfee497655d1..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()
@@ -85,8 +84,6 @@ constructor(
remember(sizedTiles) { List(sizedTiles.size) { BounceableTileViewModel() } }
val squishiness by viewModel.squishinessViewModel.squishiness.collectAsStateWithLifecycle()
val scope = rememberCoroutineScope()
- var cellIndex = 0
-
val spans by remember(sizedTiles) { derivedStateOf { sizedTiles.fastMap { it.width } } }
VerticalSpannedGrid(
@@ -95,10 +92,9 @@ constructor(
rowSpacing = dimensionResource(R.dimen.qs_tile_margin_vertical),
spans = spans,
keys = { sizedTiles[it].tile.spec },
- ) { spanIndex ->
+ ) { spanIndex, column, isFirstInColumn, isLastInColumn ->
val it = sizedTiles[spanIndex]
- val column = cellIndex % columns
- cellIndex += it.width
+
Element(it.tile.spec.toElementKey(spanIndex), Modifier) {
Tile(
tile = it.tile,
@@ -106,11 +102,21 @@ constructor(
squishiness = { squishiness },
tileHapticsViewModelFactoryProvider = tileHapticsViewModelFactoryProvider,
coroutineScope = scope,
- bounceableInfo = bounceables.bounceableInfo(it, spanIndex, column, columns),
+ bounceableInfo =
+ bounceables.bounceableInfo(
+ it,
+ index = spanIndex,
+ column = column,
+ columns = columns,
+ isFirstInRow = isFirstInColumn,
+ isLastInRow = isLastInColumn,
+ ),
detailsViewModel = detailsViewModel,
)
}
}
+
+ 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..bf63c3858542 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,6 +20,7 @@ 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
@@ -47,8 +48,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
@@ -59,7 +60,7 @@ import androidx.compose.ui.geometry.Size
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.Shape
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,6 +70,7 @@ 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
@@ -80,15 +82,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 +123,9 @@ fun TileLazyGrid(
)
}
+private val TileViewModel.traceName
+ get() = spec.toString().takeLast(Trace.MAX_SECTION_NAME_LEN)
+
@Composable
fun Tile(
tile: TileViewModel,
@@ -128,105 +137,114 @@ 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
+ // TODO(b/361789146): Draw the shapes instead of clipping
+ val tileShape by TileDefaults.animateTileShapeAsState(uiState.state)
+ val animatedColor by animateColorAsState(colors.background, label = "QSTileBackgroundColor")
+
+ 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
+ )
+ if (uiState.accessibilityUiState.toggleableState != null) {
+ coroutineScope.launch {
+ currentBounceableInfo.bounceable.animateBounce()
+ }
+ }
+ }
+ },
+ 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),
)
- 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
+ } 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,
)
- if (uiState.accessibilityUiState.toggleableState != null) {
- coroutineScope.launch { currentBounceableInfo.bounceable.animateBounce() }
- }
}
- },
- 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.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,
- )
}
}
}
@@ -255,7 +273,7 @@ private fun TileExpandable(
fun TileContainer(
onClick: () -> Unit,
onLongClick: (() -> Unit)?,
- uiState: TileUiState,
+ accessibilityUiState: AccessibilityUiState,
iconOnly: Boolean,
content: @Composable BoxScope.() -> Unit,
) {
@@ -266,17 +284,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 +306,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 +319,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 +333,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 }
}
}
@@ -356,10 +376,10 @@ 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,
@@ -418,10 +438,10 @@ private object TileDefaults {
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 +492,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 +512,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..a66b51f6fe50 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,14 @@ 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 +151,14 @@ fun InteractiveTileContainer(
onClick = onClick,
)
) {
+ val size = with(LocalDensity.current) { BadgeIconSize.toDp() }
Icon(
Icons.Default.Remove,
contentDescription = null,
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,46 @@ 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() }
Icon(
icon,
contentDescription = contentDescription,
modifier =
- Modifier.size(BadgeSize).align(Alignment.Center).drawBehind {
- drawCircle(secondaryColor)
+ Modifier.size(size).align(Alignment.Center).drawBehind {
+ drawCircle(secondaryColor, radius = BadgeSize.toPx() / 2)
},
)
}
@@ -214,7 +236,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 +245,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()
@@ -315,6 +339,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/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/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/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..7bb831baec20 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
@@ -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 48e08a0496c1..88ffd4fa2750 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/scroll/LongScreenshotActivity.java
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/scroll/LongScreenshotActivity.java
@@ -16,6 +16,8 @@
package com.android.systemui.screenshot.scroll;
+import static com.android.systemui.shared.Flags.usePreferredImageEditor;
+
import android.app.Activity;
import android.app.ActivityOptions;
import android.content.ComponentName;
@@ -350,31 +352,57 @@ 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 {
- String editorPackage = getString(R.string.config_screenshotEditor);
- Intent intent = new Intent(Intent.ACTION_EDIT);
- intent.setDataAndType(uri, "image/png");
- intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION
- | Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
- Bundle options = null;
-
- // Skip shared element transition for implicit edit intents
- if (!TextUtils.isEmpty(editorPackage)) {
- intent.setComponent(ComponentName.unflattenFromString(editorPackage));
- 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;
+ if (usePreferredImageEditor()) {
+ 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);
+ });
+ } else {
+ String editorPackage = getString(R.string.config_screenshotEditor);
+ Intent intent = new Intent(Intent.ACTION_EDIT);
+ intent.setDataAndType(uri, "image/png");
+ intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION
+ | Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
+ Bundle options = null;
+
+ // Skip shared element transition for implicit edit intents
+ if (!TextUtils.isEmpty(editorPackage)) {
+ intent.setComponent(ComponentName.unflattenFromString(editorPackage));
+ 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);
}
}
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 dde2ebc4a237..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,16 +17,17 @@
package com.android.systemui.settings.brightness
import androidx.compose.foundation.layout.fillMaxWidth
-import androidx.compose.foundation.layout.height
+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
-import com.android.systemui.qs.ui.composable.QuickSettingsShade
object ComposeDialogComposableProvider {
@@ -48,7 +49,8 @@ private fun BrightnessSliderForDialog(
}
BrightnessSliderContainer(
viewModel = viewModel,
- Modifier.fillMaxWidth().height(QuickSettingsShade.Dimensions.BrightnessSliderHeight),
+ containerColors = ContainerColors.singleColor(ContainerColors.defaultContainerColor),
+ modifier = Modifier.fillMaxWidth().padding(8.dp),
)
}
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 e4cd7ea098af..fa17b4fad592 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()) {
@@ -451,6 +438,8 @@ public class NotificationShadeWindowControllerImpl implements NotificationShadeW
} else {
mLpChanged.screenOrientation = ActivityInfo.SCREEN_ORIENTATION_NOSENSOR;
}
+ } else if (state.glanceableHubOrientationAware) {
+ mLpChanged.screenOrientation = ActivityInfo.SCREEN_ORIENTATION_USER;
} else {
mLpChanged.screenOrientation = ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED;
}
@@ -627,6 +616,7 @@ public class NotificationShadeWindowControllerImpl implements NotificationShadeW
state.shadeOrQsExpanded,
state.notificationShadeFocusable,
state.glanceableHubShowing,
+ state.glanceableHubOrientationAware,
state.bouncerShowing,
state.keyguardFadingAway,
state.keyguardGoingAway,
@@ -763,6 +753,12 @@ public class NotificationShadeWindowControllerImpl implements NotificationShadeW
}
@Override
+ public void setGlanceableHubOrientationAware(boolean isOrientationAware) {
+ mCurrentState.glanceableHubOrientationAware = isOrientationAware;
+ apply(mCurrentState);
+ }
+
+ @Override
public void setBackdropShowing(boolean showing) {
mCurrentState.mediaBackdropShowing = showing;
apply(mCurrentState);
diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowState.kt b/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowState.kt
index 6a4b52af498c..a1eac745b3a1 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowState.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowState.kt
@@ -36,6 +36,7 @@ class NotificationShadeWindowState(
@JvmField var notificationShadeFocusable: Boolean = false,
@JvmField var bouncerShowing: Boolean = false,
@JvmField var glanceableHubShowing: Boolean = false,
+ @JvmField var glanceableHubOrientationAware: Boolean = false,
@JvmField var keyguardFadingAway: Boolean = false,
@JvmField var keyguardGoingAway: Boolean = false,
@JvmField var qsExpanded: Boolean = false,
@@ -81,6 +82,7 @@ class NotificationShadeWindowState(
notificationShadeFocusable.toString(),
bouncerShowing.toString(),
glanceableHubShowing.toString(),
+ glanceableHubOrientationAware.toString(),
keyguardFadingAway.toString(),
keyguardGoingAway.toString(),
qsExpanded.toString(),
@@ -122,6 +124,7 @@ class NotificationShadeWindowState(
panelExpanded: Boolean,
notificationShadeFocusable: Boolean,
glanceableHubShowing: Boolean,
+ glanceableHubOrientationAware: Boolean,
bouncerShowing: Boolean,
keyguardFadingAway: Boolean,
keyguardGoingAway: Boolean,
@@ -153,6 +156,7 @@ class NotificationShadeWindowState(
this.shadeOrQsExpanded = panelExpanded
this.notificationShadeFocusable = notificationShadeFocusable
this.glanceableHubShowing = glanceableHubShowing
+ this.glanceableHubOrientationAware = glanceableHubOrientationAware
this.bouncerShowing = bouncerShowing
this.keyguardFadingAway = keyguardFadingAway
this.keyguardGoingAway = keyguardGoingAway
@@ -202,6 +206,7 @@ class NotificationShadeWindowState(
"panelExpanded",
"notificationShadeFocusable",
"glanceableHubShowing",
+ "glanceableHubOrientationAware",
"bouncerShowing",
"keyguardFadingAway",
"keyguardGoingAway",
@@ -223,7 +228,7 @@ class NotificationShadeWindowState(
"dozing",
"scrimsVisibility",
"backgroundBlurRadius",
- "communalVisible"
+ "communalVisible",
)
}
}
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/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 246177e0c46d..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> =
@@ -334,7 +334,7 @@ constructor(
} else if (state.fromContent == overlay) {
state.progress.map { progress -> 1 - progress }
} else {
- flowOf(0f)
+ state.currentOverlays().map { if (overlay in it) 1f else 0f }
}
}
}
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/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/NotificationGroupingUtil.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationGroupingUtil.java
index 4bb12e59a877..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;
@@ -57,6 +61,12 @@ public class NotificationGroupingUtil {
private static final VisibilityApplicator VISIBILITY_APPLICATOR = new VisibilityApplicator();
private static final VisibilityApplicator APP_NAME_APPLICATOR = new AppNameApplicator();
private static final ResultApplicator LEFT_ICON_APPLICATOR = new LeftIconApplicator();
+ private static final DataExtractor ICON_EXTRACTOR = new DataExtractor() {
+ @Override
+ public Object extractData(ExpandableNotificationRow row) {
+ return row.getEntry().getSbn().getNotification();
+ }
+ };
private final ExpandableNotificationRow mRow;
private final ArrayList<Processor> mProcessors = new ArrayList<>();
@@ -66,6 +76,7 @@ public class NotificationGroupingUtil {
mRow = row;
final IconComparator iconVisibilityComparator = new IconComparator() {
+ @Override
public boolean compare(View parent, View child, Object parentData,
Object childData) {
if (Flags.notificationsRedesignAppIcons() && mRow.isShowingAppIcon()) {
@@ -77,6 +88,7 @@ public class NotificationGroupingUtil {
}
};
final IconComparator greyComparator = new IconComparator() {
+ @Override
public boolean compare(View parent, View child, Object parentData,
Object childData) {
if (Flags.notificationsRedesignAppIcons() && mRow.isShowingAppIcon()) {
@@ -103,26 +115,31 @@ public class NotificationGroupingUtil {
// To hide the icons if they are the same and the color is the same
mProcessors.add(new Processor(mRow,
com.android.internal.R.id.icon,
+ ICON_EXTRACTOR,
iconVisibilityComparator,
VISIBILITY_APPLICATOR));
// To grey out the icons when they are not the same, or they have the same color
mProcessors.add(new Processor(mRow,
com.android.internal.R.id.status_bar_latest_event_content,
+ ICON_EXTRACTOR,
greyComparator,
greyApplicator));
// To show the large icon on the left side instead if all the small icons are the same
mProcessors.add(new Processor(mRow,
com.android.internal.R.id.status_bar_latest_event_content,
+ ICON_EXTRACTOR,
iconVisibilityComparator,
LEFT_ICON_APPLICATOR));
// To only show the work profile icon in the group header
mProcessors.add(new Processor(mRow,
com.android.internal.R.id.profile_badge,
+ null /* Extractor */,
BADGE_COMPARATOR,
VISIBILITY_APPLICATOR));
// To hide the app name in group children
mProcessors.add(new Processor(mRow,
com.android.internal.R.id.app_name_text,
+ null,
APP_NAME_COMPARATOR,
APP_NAME_APPLICATOR));
// To hide the header text if it's the same
@@ -201,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;
@@ -230,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.
*/
@@ -242,20 +270,23 @@ public class NotificationGroupingUtil {
private static class Processor {
private final int mId;
+ private final DataExtractor mExtractor;
private final ViewComparator mComparator;
private final ResultApplicator mApplicator;
private final ExpandableNotificationRow mParentRow;
private boolean mApply;
private View mParentView;
+ private Object mParentData;
public static Processor forTextView(ExpandableNotificationRow row, int id) {
- return new Processor(row, id, TEXT_VIEW_COMPARATOR, VISIBILITY_APPLICATOR);
+ return new Processor(row, id, null, TEXT_VIEW_COMPARATOR, VISIBILITY_APPLICATOR);
}
- Processor(ExpandableNotificationRow row, int id,
+ Processor(ExpandableNotificationRow row, int id, DataExtractor extractor,
ViewComparator comparator,
ResultApplicator applicator) {
mId = id;
+ mExtractor = extractor;
mApplicator = applicator;
mComparator = comparator;
mParentRow = row;
@@ -265,6 +296,7 @@ public class NotificationGroupingUtil {
NotificationViewWrapper wrapper = mParentRow.getNotificationViewWrapper();
View header = wrapper == null ? null : wrapper.getNotificationHeader();
mParentView = header == null ? null : header.findViewById(mId);
+ mParentData = mExtractor == null ? null : mExtractor.extractData(mParentRow);
mApply = !mComparator.isEmpty(mParentView);
}
@@ -282,7 +314,9 @@ public class NotificationGroupingUtil {
// when for example showing an undo notification
return;
}
- mApply = mComparator.compare(mParentView, ownView);
+ Object childData = mExtractor == null ? null : mExtractor.extractData(row);
+ mApply = mComparator.compare(mParentView, ownView,
+ mParentData, childData);
}
public void apply(ExpandableNotificationRow row) {
@@ -314,9 +348,11 @@ public class NotificationGroupingUtil {
/**
* @param parent the view with the given id in the group header
* @param child the view with the given id in the child notification
+ * @param parentData optional data for the parent
+ * @param childData optional data for the child
* @return whether to views are the same
*/
- boolean compare(View parent, View child);
+ boolean compare(View parent, View child, Object parentData, Object childData);
boolean isEmpty(View view);
}
@@ -327,7 +363,7 @@ public class NotificationGroupingUtil {
private static class BadgeComparator implements ViewComparator {
@Override
- public boolean compare(View parent, View child) {
+ public boolean compare(View parent, View child, Object parentData, Object childData) {
return parent.getVisibility() != View.GONE;
}
@@ -345,7 +381,7 @@ public class NotificationGroupingUtil {
private static class TextViewComparator implements ViewComparator {
@Override
- public boolean compare(View parent, View child) {
+ public boolean compare(View parent, View child, Object parentData, Object childData) {
TextView parentView = (TextView) parent;
CharSequence parentText = parentView == null ? "" : parentView.getText();
TextView childView = (TextView) child;
@@ -361,7 +397,7 @@ public class NotificationGroupingUtil {
private abstract static class IconComparator implements ViewComparator {
@Override
- public boolean compare(View parent, View child) {
+ public boolean compare(View parent, View child, Object parentData, Object childData) {
return false;
}
@@ -421,14 +457,14 @@ public class NotificationGroupingUtil {
private static class AppNameComparator extends TextViewComparator {
@Override
- public boolean compare(View parent, View child) {
+ public boolean compare(View parent, View child, Object parentData, Object childData) {
if (isEmpty(child)) {
// In headerless notifications the AppName view exists but is usually GONE (and not
// populated). We need to treat this case as equal to the header in order to
// deduplicate the view.
return true;
}
- return super.compare(parent, child);
+ return super.compare(parent, child, parentData, childData);
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerImpl.java
index 32da6fff6bcc..ec04edb0f810 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerImpl.java
@@ -47,6 +47,7 @@ import android.database.ExecutorContentObserver;
import android.net.Uri;
import android.os.Looper;
import android.os.Process;
+import android.os.SystemClock;
import android.os.UserHandle;
import android.os.UserManager;
import android.provider.Settings;
@@ -75,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;
@@ -809,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;
}
@@ -817,6 +823,7 @@ public class NotificationLockscreenUserManagerImpl implements
return false;
}
+ long notificationTime = getEarliestNotificationTime(ent);
if (!mRedactOtpOnWifi.get()) {
if (mConnectedToWifi.get()) {
return false;
@@ -824,7 +831,7 @@ public class NotificationLockscreenUserManagerImpl implements
long lastWifiConnectTime = mLastWifiConnectionTime.get();
// If the device has connected to wifi since receiving the notification, do not redact
- if (ent.getSbn().getPostTime() < lastWifiConnectTime) {
+ if (notificationTime < lastWifiConnectTime) {
return false;
}
}
@@ -837,13 +844,87 @@ public class NotificationLockscreenUserManagerImpl implements
// when this notification arrived, do not redact
long latestTimeForRedaction = mLastLockTime.get() + mOtpRedactionRequiredLockTimeMs.get();
- if (ent.getSbn().getPostTime() < latestTimeForRedaction) {
+ if (notificationTime < latestTimeForRedaction) {
return false;
}
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 creationTimeWallClock = System.currentTimeMillis() - creationTimeDelta;
+ return Math.min(notifWhenWallClock, creationTimeWallClock);
+ }
+
private boolean packageHasVisibilityOverride(String key) {
if (mCommonNotifCollectionLazy.get() == null) {
Log.wtf(TAG, "mEntryManager was null!", new Throwable());
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..bdfdb8191356 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShadeDepthController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShadeDepthController.kt
@@ -38,13 +38,13 @@ import com.android.systemui.Flags
import com.android.systemui.Flags.spatialModelAppPushback
import com.android.systemui.animation.ShadeInterpolation
import com.android.systemui.dagger.SysUISingleton
-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.shared.Flags.ambientAod
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
@@ -53,16 +53,15 @@ 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
@@ -79,14 +78,12 @@ constructor(
private val keyguardInteractor: KeyguardInteractor,
private val choreographer: Choreographer,
private val wallpaperController: WallpaperController,
- private val wallpaperInteractor: WallpaperInteractor,
private val notificationShadeWindowController: NotificationShadeWindowController,
private val dozeParameters: DozeParameters,
@ShadeDisplayAware private val context: Context,
private val splitShadeStateController: SplitShadeStateController,
private val windowRootViewBlurInteractor: WindowRootViewBlurInteractor,
private val appZoomOutOptional: Optional<AppZoomOut>,
- @Application private val applicationScope: CoroutineScope,
dumpManager: DumpManager,
configurationController: ConfigurationController,
) : ShadeExpansionListener, Dumpable {
@@ -97,6 +94,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"
}
@@ -115,8 +113,6 @@ constructor(
private var prevTimestamp: Long = -1
private var prevShadeDirection = 0
private var prevShadeVelocity = 0f
- private var prevDozeAmount: Float = 0f
- @VisibleForTesting var wallpaperSupportsAmbientMode: Boolean = false
// tracks whether app launch transition is in progress. This involves two independent factors
// that control blur, shade expansion and app launch animation from outside sysui.
// They can complete out of order, this flag will be reset by the animation that finishes later.
@@ -163,6 +159,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 +191,12 @@ constructor(
return
}
- shadeAnimation.animateTo(0)
- shadeAnimation.finishIfRunning()
+ if (Flags.notificationShadeBlur()) {
+ shadeAnimation.skipTo(0)
+ } else {
+ shadeAnimation.animateTo(0)
+ shadeAnimation.finishIfRunning()
+ }
}
@Deprecated(
message =
@@ -226,15 +229,7 @@ constructor(
}
/** Blur radius of the wake-up animation on this frame. */
- private var wakeBlurRadius = 0f
- set(value) {
- if (field == value) return
- field = value
- scheduleUpdate()
- }
-
- /** Blur radius of the unlock animation on this frame. */
- private var unlockBlurRadius = 0f
+ private var wakeAndUnlockBlurRadius = 0f
set(value) {
if (field == value) return
field = value
@@ -261,16 +256,14 @@ 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, wakeAndUnlockBlurRadius)
if (areBlursDisabledForAppLaunch || blursDisabledForUnlock) {
shadeRadius = 0f
}
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
+ val zoomOut = blurRadiusToZoomOut(blurRadius = shadeRadius)
// Make blur be 0 if it is necessary to stop blur effect.
if (scrimsVisible) {
if (!Flags.notificationShadeBlur()) {
@@ -355,14 +348,14 @@ constructor(
startDelay = keyguardStateController.keyguardFadingAwayDelay
interpolator = Interpolators.FAST_OUT_SLOW_IN
addUpdateListener { animation: ValueAnimator ->
- unlockBlurRadius =
+ wakeAndUnlockBlurRadius =
blurUtils.blurRadiusOfRatio(animation.animatedValue as Float)
}
addListener(
object : AnimatorListenerAdapter() {
override fun onAnimationEnd(animation: Animator) {
keyguardAnimator = null
- unlockBlurRadius = 0f
+ wakeAndUnlockBlurRadius = 0f
}
}
)
@@ -398,20 +391,15 @@ constructor(
}
override fun onDozeAmountChanged(linear: Float, eased: Float) {
- prevDozeAmount = eased
- updateWakeBlurRadius(prevDozeAmount)
+ wakeAndUnlockBlurRadius =
+ if (ambientAod()) {
+ 0f
+ } else {
+ blurUtils.blurRadiusOfRatio(eased)
+ }
}
}
- private fun updateWakeBlurRadius(ratio: Float) {
- wakeBlurRadius =
- if (!wallpaperSupportsAmbientMode) {
- 0f
- } else {
- blurUtils.blurRadiusOfRatio(ratio)
- }
- }
-
init {
dumpManager.registerCriticalDumpable(javaClass.name, this)
if (WAKE_UP_ANIMATION_ENABLED) {
@@ -433,12 +421,6 @@ constructor(
}
}
)
- applicationScope.launch {
- wallpaperInteractor.wallpaperSupportsAmbientMode.collect { supported ->
- wallpaperSupportsAmbientMode = supported
- updateWakeBlurRadius(prevDozeAmount)
- }
- }
initBlurListeners()
}
@@ -515,6 +497,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 +611,7 @@ 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("wakeAndUnlockBlur: $wakeAndUnlockBlurRadius")
it.println("blursDisabledForAppLaunch: $blursDisabledForAppLaunch")
it.println("appLaunchTransitionIsInProgress: $appLaunchTransitionIsInProgress")
it.println("qsPanelExpansion: $qsPanelExpansion")
@@ -660,6 +657,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 +679,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/NotificationShadeWindowController.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShadeWindowController.java
index 85fad420daf1..50cf015af5e3 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShadeWindowController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShadeWindowController.java
@@ -89,6 +89,9 @@ public interface NotificationShadeWindowController extends RemoteInputController
/** Sets the state of whether the glanceable hub is showing or not. */
default void setGlanceableHubShowing(boolean showing) {}
+ /** Sets the state of whether the glanceable hub can change with user's orientation or not. */
+ default void setGlanceableHubOrientationAware(boolean isOrientationAware) {}
+
/** Sets the state of whether the backdrop is showing or not. */
default void setBackdropShowing(boolean showing) {}
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..2fe627020ebf 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()
})
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..6f8552738d33 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.")
}
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..55d753662a65 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 =
@@ -86,7 +91,7 @@ fun ChipContent(viewModel: OngoingActivityChipModel.Active, modifier: Modifier =
startPadding = startPadding,
endPadding = endPadding,
)
- .neverDecreaseWidth(),
+ .neverDecreaseWidth(density),
)
}
@@ -97,7 +102,7 @@ fun ChipContent(viewModel: OngoingActivityChipModel.Active, modifier: Modifier =
style = textStyle,
color = textColor,
softWrap = false,
- modifier = modifier.neverDecreaseWidth(),
+ modifier = modifier.neverDecreaseWidth(density),
)
}
@@ -150,23 +155,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..7080c3402b08 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
@@ -47,7 +48,13 @@ fun OngoingActivityChips(
chips.active
.filter { !it.isHidden }
.forEach {
- key(it.key) { OngoingActivityChip(model = it, iconViewStore = iconViewStore) }
+ 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..8e470742f174 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,6 +16,8 @@
package com.android.systemui.statusbar.chips.ui.model
+import android.annotation.CurrentTimeMillisLong
+import android.annotation.ElapsedRealtimeLong
import android.view.View
import com.android.systemui.animation.Expandable
import com.android.systemui.common.shared.model.ContentDescription
@@ -102,7 +104,7 @@ sealed class OngoingActivityChipModel {
* [ChipChronometer] is based off of elapsed realtime. See
* [android.widget.Chronometer.setBase].
*/
- val startTimeMs: Long,
+ @ElapsedRealtimeLong val startTimeMs: Long,
override val onClickListenerLegacy: View.OnClickListener?,
override val clickBehavior: ClickBehavior,
override val isHidden: Boolean = false,
@@ -129,10 +131,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 +155,7 @@ sealed class OngoingActivityChipModel {
shouldAnimate,
) {
init {
- StatusBarNotifChips.assertInNewMode()
+ StatusBarNotifChips.unsafeAssertInNewMode()
}
override val logName = "Active.ShortTimeDelta"
@@ -227,7 +234,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/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/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 74faf2576abd..68c13afe82dc 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/PhysicsPropertyAnimator.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/PhysicsPropertyAnimator.kt
@@ -15,12 +15,12 @@
*/
package com.android.systemui.statusbar.notification
+import android.util.FloatProperty
import android.util.Property
import android.view.View
-import androidx.dynamicanimation.animation.DynamicAnimation
-import androidx.dynamicanimation.animation.FloatPropertyCompat
-import androidx.dynamicanimation.animation.SpringAnimation
-import androidx.dynamicanimation.animation.SpringForce
+import com.android.internal.dynamicanimation.animation.DynamicAnimation
+import com.android.internal.dynamicanimation.animation.SpringAnimation
+import com.android.internal.dynamicanimation.animation.SpringForce
import com.android.systemui.res.R
import com.android.systemui.statusbar.notification.PhysicsPropertyAnimator.Companion.createDefaultSpring
import com.android.systemui.statusbar.notification.stack.AnimationProperties
@@ -33,8 +33,8 @@ import com.android.systemui.statusbar.notification.stack.AnimationProperties
*/
data class PhysicsProperty(val tag: Int, val property: Property<View, Float>) {
val offsetProperty =
- object : FloatPropertyCompat<View>(property.name) {
- override fun getValue(view: View): Float {
+ object : FloatProperty<View>(property.name) {
+ override fun get(view: View): Float {
return property.get(view)
}
@@ -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..4d68f2e6ef1b 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
@@ -45,8 +45,6 @@ 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);
@@ -55,7 +53,6 @@ public class BundleEntry extends PipelineEntry {
public BundleEntry(String key) {
super(key);
- mEntryAdapter = new BundleEntryAdapter();
}
@Nullable
@@ -66,17 +63,6 @@ public class BundleEntry extends PipelineEntry {
@Nullable
@Override
- public NotifSection getSection() {
- return null;
- }
-
- @Override
- public int getSectionIndex() {
- return 0;
- }
-
- @Nullable
- @Override
public PipelineEntry getParent() {
return null;
}
@@ -86,126 +72,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 +82,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..26c302bf6409
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/BundleEntryAdapter.kt
@@ -0,0 +1,131 @@
+/*
+ * 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 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..3118ce56ac69 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();
/**
@@ -135,5 +135,15 @@ public interface EntryAdapter {
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..3d8ba7f335bd 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
@@ -109,7 +109,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 +269,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
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..1168c647c26a
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationEntryAdapter.kt
@@ -0,0 +1,151 @@
+/*
+ * 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 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..238ba8d9f490 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,21 @@ 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()) {
+ mIdToBundleEntry.put(id, new BundleEntry(id));
+ }
+ }
+
void setNotifStabilityManager(@NonNull NotifStabilityManager notifStabilityManager) {
Assert.isMainThread();
mPipelineState.requireState(STATE_IDLE);
@@ -297,6 +317,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 +679,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 +687,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..8833ff1ce20c 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,20 @@ class BundleCoordinator @Inject constructor(
}
}
+ val bundler =
+ object : NotifBundler("NotifBundler") {
+
+ // Use list instead of set to keep fixed order
+ override val bundleIds: List<String> = SYSTEM_RESERVED_IDS
+
+ override fun getBundleIdOrNull(entry: NotificationEntry?): String? {
+ return entry?.representativeEntry?.channel?.id?.takeIf { it in this.bundleIds }
+ }
+ }
+
override fun attach(pipeline: NotifPipeline) {
+ if (NotificationBundleUi.isEnabled) {
+ pipeline.setNotifBundler(bundler)
+ }
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/ConversationCoordinator.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/ConversationCoordinator.kt
index 248b5286803f..28923b0ab71e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/ConversationCoordinator.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/ConversationCoordinator.kt
@@ -16,6 +16,7 @@
package com.android.systemui.statusbar.notification.collection.coordinator
+import android.app.NotificationChannel.SYSTEM_RESERVED_IDS
import com.android.systemui.statusbar.notification.collection.GroupEntry
import com.android.systemui.statusbar.notification.collection.PipelineEntry
import com.android.systemui.statusbar.notification.collection.NotifPipeline
@@ -89,6 +90,7 @@ class ConversationCoordinator @Inject constructor(
object : NotifSectioner("Priority People", BUCKET_PRIORITY_PEOPLE) {
override fun isInSection(entry: PipelineEntry): Boolean {
return getPeopleType(entry) == TYPE_IMPORTANT_PERSON
+ && entry.representativeEntry?.channel?.id !in SYSTEM_RESERVED_IDS
}
}
@@ -96,10 +98,12 @@ class ConversationCoordinator @Inject constructor(
val peopleAlertingSectioner = object : NotifSectioner("People(alerting)", BUCKET_PEOPLE) {
override fun isInSection(entry: PipelineEntry): Boolean {
if (SortBySectionTimeFlag.isEnabled) {
- return highPriorityProvider.isHighPriorityConversation(entry)
- || isConversation(entry)
+ return (highPriorityProvider.isHighPriorityConversation(entry)
+ || isConversation(entry))
+ && entry.representativeEntry?.channel?.id !in SYSTEM_RESERVED_IDS
} else {
return highPriorityProvider.isHighPriorityConversation(entry)
+ && entry.representativeEntry?.channel?.id !in SYSTEM_RESERVED_IDS
}
}
@@ -111,11 +115,12 @@ class ConversationCoordinator @Inject constructor(
}
val peopleSilentSectioner = object : NotifSectioner("People(silent)", BUCKET_PEOPLE) {
- // Because the peopleAlertingSectioner is above this one, it will claim all conversations that are alerting.
- // All remaining conversations must be silent.
+ // Because the peopleAlertingSectioner is above this one, it will claim all conversations
+ // that are alerting. All remaining conversations must be silent.
override fun isInSection(entry: PipelineEntry): Boolean {
SortBySectionTimeFlag.assertInLegacyMode()
return isConversation(entry)
+ && entry.representativeEntry?.channel?.id !in SYSTEM_RESERVED_IDS
}
override fun getComparator(): NotifComparator {
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..3fad8f0510a1 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
@@ -132,7 +132,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 +152,7 @@ public class RankingCoordinator implements Coordinator {
@Override
public boolean isInSection(PipelineEntry entry) {
return !mHighPriorityProvider.isHighPriority(entry)
+ && entry.getRepresentativeEntry() != null
&& entry.getRepresentativeEntry().isAmbient();
}
@@ -161,7 +167,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/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/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/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 7959e99f3189..2a01a14f56aa 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
@@ -20,6 +20,7 @@ import android.app.Flags
import android.app.Flags.notificationsRedesignTemplates
import android.app.Notification
import android.graphics.PorterDuff
+import android.util.Log
import android.view.LayoutInflater
import android.view.View
import android.view.View.GONE
@@ -80,9 +81,13 @@ fun AODPromotedNotification(
val content = viewModel.content ?: return
val audiblyAlertedIconVisible = viewModel.audiblyAlertedIconVisible
- key(content.identity) {
- val layoutResource = content.layoutResource ?: return
+ val layoutResource = content.layoutResource
+ if (layoutResource == null) {
+ Log.w(TAG, "not displaying promoted notif with ineligible style on AOD")
+ return
+ }
+ key(content.identity) {
val sidePaddings = dimensionResource(systemuiR.dimen.notification_side_paddings)
val sidePaddingValues = PaddingValues(horizontal = sidePaddings, vertical = 0.dp)
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/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 a081ad5bb82c..689222608abe 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
@@ -119,6 +119,7 @@ public abstract class ActivatableNotificationView extends ExpandableOutlineView
protected Point mTargetPoint;
private boolean mDismissed;
private boolean mRefocusOnDismiss;
+ protected boolean mIsBlurSupported;
public ActivatableNotificationView(Context context, AttributeSet attrs) {
super(context, attrs);
@@ -127,13 +128,19 @@ public abstract class ActivatableNotificationView extends ExpandableOutlineView
updateColors();
}
- private void updateColors() {
+ protected void updateColors() {
if (notificationRowTransparency()) {
- mNormalColor = SurfaceEffectColors.surfaceEffect1(getContext());
+ if (mIsBlurSupported) {
+ mNormalColor = SurfaceEffectColors.surfaceEffect1(getContext());
+ } else {
+ mNormalColor = 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(
@@ -144,6 +151,12 @@ 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.
*/
@@ -173,6 +186,7 @@ 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();
@@ -326,6 +340,21 @@ public abstract class ActivatableNotificationView extends ExpandableOutlineView
mBackgroundNormal.setBottomAmountClips(!isChildInGroup());
}
+ public void setIsBlurSupported(boolean isBlurSupported) {
+ if (!notificationRowTransparency()) {
+ return;
+ }
+ boolean usedTransparentBackground = usesTransparentBackground();
+ mIsBlurSupported = isBlurSupported;
+ if (usedTransparentBackground != usesTransparentBackground()) {
+ updateBackgroundColors();
+ }
+ }
+
+ protected boolean usesTransparentBackground() {
+ return mIsBlurSupported && notificationRowTransparency();
+ }
+
@Override
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
super.onLayout(changed, left, top, right, bottom);
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 179951f67902..b481455699ef 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.notificationsPinnedHunInShade;
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;
@@ -99,6 +103,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 +115,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 +123,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 +186,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 +409,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 +431,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 +871,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 +952,7 @@ public class ExpandableNotificationRow extends ActivatableNotificationView
@Nullable
public EntryAdapter getEntryAdapter() {
- NotificationBundleUi.assertInNewMode();
+ NotificationBundleUi.unsafeAssertInNewMode();
return mEntryAdapter;
}
@@ -974,7 +979,7 @@ public class ExpandableNotificationRow extends ActivatableNotificationView
} else if (isAboveShelf() != wasAboveShelf) {
mAboveShelfChangedListener.onAboveShelfStateChanged(!wasAboveShelf);
}
- updateBackgroundOpacity();
+ updateColors();
}
/**
@@ -1345,7 +1350,7 @@ public class ExpandableNotificationRow extends ActivatableNotificationView
if (mIsSummaryWithChildren) {
return mChildrenContainer.getIntrinsicHeight();
}
- if (isPromotedOngoing()) {
+ if (PromotedNotificationUiForceExpanded.isEnabled() && isPromotedOngoing()) {
return getMaxExpandHeight();
}
if (mExpandedWhenPinned) {
@@ -1678,9 +1683,8 @@ public class ExpandableNotificationRow extends ActivatableNotificationView
if (view != null) {
view.setBackgroundTintColor(color);
}
- if (notificationRowTransparency()
- && (mBackgroundNormal != null)) {
- if (NotificationBundleUi.isEnabled()) {
+ if (notificationRowTransparency() && mBackgroundNormal != null) {
+ if (NotificationBundleUi.isEnabled() && mEntryAdapter != null) {
mBackgroundNormal.setBgIsColorized(mEntryAdapter.isColorized());
} else {
if (mEntry != null) {
@@ -1815,6 +1819,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;
@@ -2073,7 +2093,7 @@ public class ExpandableNotificationRow extends ActivatableNotificationView
*/
public ExpandableNotificationRow(Context context, AttributeSet attrs, UserHandle user) {
this(context, attrs, userContextForEntry(context, user));
- NotificationBundleUi.assertInNewMode();
+ NotificationBundleUi.unsafeAssertInNewMode();
}
/**
@@ -2121,7 +2141,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,
@@ -2149,11 +2170,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;
@@ -2754,7 +2775,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
@@ -2919,7 +2939,7 @@ public class ExpandableNotificationRow extends ActivatableNotificationView
if (mIsSummaryWithChildren && !shouldShowPublic()) {
return !mChildrenExpanded;
}
- if (isPromotedOngoing()) {
+ if (PromotedNotificationUiForceExpanded.isEnabled() && isPromotedOngoing()) {
return false;
}
return mEnableNonGroupedNotificationExpand && mExpandable;
@@ -2930,16 +2950,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
@@ -3017,7 +3050,7 @@ public class ExpandableNotificationRow extends ActivatableNotificationView
}
public void setUserLocked(boolean userLocked) {
- if (isPromotedOngoing()) return;
+ if (PromotedNotificationUiForceExpanded.isEnabled() && isPromotedOngoing()) return;
mUserLocked = userLocked;
mPrivateLayout.setUserExpanding(userLocked);
@@ -3077,7 +3110,7 @@ public class ExpandableNotificationRow extends ActivatableNotificationView
mChildrenContainer.setOnKeyguard(onKeyguard);
}
}
- updateBackgroundOpacity();
+ updateColors();
}
}
@@ -3148,6 +3181,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
@@ -3197,7 +3236,16 @@ public class ExpandableNotificationRow extends ActivatableNotificationView
}
public boolean isPromotedOngoing() {
- return PromotedNotificationUiForceExpanded.isEnabled() && mIsPromotedOngoing;
+ if (!PromotedNotificationUi.isEnabled()) {
+ return false;
+ }
+
+ final NotificationEntry entry = mEntry;
+ if (entry == null) {
+ return false;
+ }
+
+ return entry.isPromotedOngoing();
}
private boolean isPromotedNotificationExpanded(boolean allowOnKeyguard) {
@@ -3226,6 +3274,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
@@ -3238,7 +3307,7 @@ public class ExpandableNotificationRow extends ActivatableNotificationView
}
public boolean isExpanded(boolean allowOnKeyguard) {
- if (isPromotedOngoing()) {
+ if (PromotedNotificationUiForceExpanded.isEnabled() && isPromotedOngoing()) {
return isPromotedNotificationExpanded(allowOnKeyguard);
}
@@ -3907,9 +3976,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);
@@ -3962,6 +4029,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);
}
}
@@ -3992,33 +4071,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());
@@ -4031,6 +4127,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)) {
@@ -4294,8 +4396,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);
@@ -4524,11 +4628,8 @@ 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() {
+ return super.usesTransparentBackground() && !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..d02f636728fc 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
@@ -42,14 +42,6 @@ class MagicActionBackgroundDrawable(
private val buttonShape = Path()
// Color and style
- private val bgPaint = Paint(Paint.ANTI_ALIAS_FLAG).apply {
- val bgColor =
- context.getColor(
- com.android.internal.R.color.materialColorPrimaryContainer
- )
- color = bgColor
- style = Paint.Style.FILL
- }
private val outlinePaint = Paint(Paint.ANTI_ALIAS_FLAG).apply {
val outlineColor =
context.getColor(
@@ -99,7 +91,6 @@ class MagicActionBackgroundDrawable(
canvas.save()
// Draw background
canvas.clipPath(buttonShape)
- canvas.drawPath(buttonShape, bgPaint)
// Apply gradient to outline
canvas.drawPath(buttonShape, outlinePaint)
updateGradient(boundsF)
@@ -128,13 +119,11 @@ class MagicActionBackgroundDrawable(
}
override fun setAlpha(alpha: Int) {
- bgPaint.alpha = alpha
outlinePaint.alpha = alpha
invalidateSelf()
}
override fun setColorFilter(colorFilter: ColorFilter?) {
- bgPaint.colorFilter = colorFilter
outlinePaint.colorFilter = colorFilter
invalidateSelf()
}
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 4978fa4880aa..4914e1073059 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
@@ -39,7 +39,6 @@ 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;
import com.android.systemui.res.R;
import com.android.systemui.statusbar.notification.shared.NotificationAddXOnHoverToDismiss;
import com.android.systemui.util.DrawableDumpKt;
@@ -73,7 +72,7 @@ public class NotificationBackgroundView extends View implements Dumpable,
private int mDrawableAlpha = 255;
private final ColorStateList mLightColoredStatefulColors;
private final ColorStateList mDarkColoredStatefulColors;
- private final int mNormalColor;
+ private int mNormalColor;
private boolean mBgIsColorized = false;
private boolean mForceOpaque = false;
private final int convexR = 9;
@@ -89,15 +88,13 @@ 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()) {
@@ -158,14 +155,6 @@ public class NotificationBackgroundView extends View implements Dumpable,
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.
@@ -329,10 +318,7 @@ public class NotificationBackgroundView extends View implements Dumpable,
// 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);
- }
+ : mNormalColor;
getBaseBackgroundLayer().setColorFilter(
new PorterDuffColorFilter(
color,
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..3ffc052b5acc 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
@@ -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;
@@ -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");
@@ -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) {
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..b1c145e08777 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
@@ -53,7 +53,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 +498,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 +687,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,7 +756,7 @@ constructor(
) {
logger.logAsyncTaskProgress(
entry.logKey,
- "inflating public single line view model"
+ "inflating public single line view model",
)
if (bindParams.redactionType == REDACTION_TYPE_SENSITIVE_CONTENT) {
SingleLineViewInflater.inflateSingleLineViewModel(
@@ -852,18 +858,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,10 +873,7 @@ 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
@@ -1142,7 +1139,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 +1195,7 @@ constructor(
}
logger.logAsyncTaskProgress(
entry.logKey,
- "applying low priority group header view"
+ "applying low priority group header view",
)
applyRemoteView(
inflationExecutor = inflationExecutor,
@@ -1295,6 +1292,7 @@ constructor(
runningInflations,
e,
row,
+ entry,
callback,
logger,
"applying view synchronously",
@@ -1320,6 +1318,7 @@ constructor(
runningInflations,
InflationException(invalidReason),
row,
+ entry,
callback,
logger,
"applied invalid view",
@@ -1379,6 +1378,7 @@ constructor(
runningInflations,
e,
row,
+ entry,
callback,
logger,
"applying view",
@@ -1482,6 +1482,7 @@ constructor(
runningInflations: HashMap<Int, CancellationSignal>,
e: Exception,
notification: ExpandableNotificationRow?,
+ entry: NotificationEntry,
callback: InflationCallback?,
logger: NotificationRowContentBinderLogger,
logContext: String,
@@ -1489,7 +1490,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 +1523,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/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/ui/viewbinder/ActivatableNotificationViewBinder.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ui/viewbinder/ActivatableNotificationViewBinder.kt
index f0b5c3667962..8984f2c4220d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ui/viewbinder/ActivatableNotificationViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ui/viewbinder/ActivatableNotificationViewBinder.kt
@@ -50,6 +50,15 @@ object ActivatableNotificationViewBinder {
view.registerListenersWhileAttached(touchHandler)
}
}
+ view.repeatWhenAttached {
+ repeatOnLifecycle(Lifecycle.State.STARTED) {
+ launch {
+ viewModel.isBlurSupported.collect { supported ->
+ view.setIsBlurSupported(supported)
+ }
+ }
+ }
+ }
}
private suspend fun ActivatableNotificationView.registerListenersWhileAttached(
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ui/viewmodel/ActivatableNotificationViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ui/viewmodel/ActivatableNotificationViewModel.kt
index f46d42473863..9a86ffbe33b6 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ui/viewmodel/ActivatableNotificationViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ui/viewmodel/ActivatableNotificationViewModel.kt
@@ -17,6 +17,7 @@
package com.android.systemui.statusbar.notification.row.ui.viewmodel
import com.android.systemui.accessibility.domain.interactor.AccessibilityInteractor
+import com.android.systemui.window.domain.interactor.WindowRootViewBlurInteractor
import dagger.Module
import dagger.Provides
import kotlinx.coroutines.flow.Flow
@@ -26,25 +27,34 @@ import kotlinx.coroutines.flow.map
interface ActivatableNotificationViewModel : ExpandableOutlineViewModel {
/** Does the view react to touches? */
val isTouchable: Flow<Boolean>
+ val isBlurSupported: Flow<Boolean>
companion object {
operator fun invoke(
a11yInteractor: AccessibilityInteractor,
- ): ActivatableNotificationViewModel = ActivatableNotificationViewModelImpl(a11yInteractor)
+ windowRootViewBlurInteractor: WindowRootViewBlurInteractor
+ ): ActivatableNotificationViewModel =
+ ActivatableNotificationViewModelImpl(a11yInteractor, windowRootViewBlurInteractor)
}
}
private class ActivatableNotificationViewModelImpl(
a11yInteractor: AccessibilityInteractor,
+ windowRootViewBlurInteractor: WindowRootViewBlurInteractor,
) : ActivatableNotificationViewModel {
override val isTouchable: Flow<Boolean> =
// If a11y touch exploration is enabled, then the activatable view should ignore touches
a11yInteractor.isTouchExplorationEnabled.map { !it }
+ override val isBlurSupported: Flow<Boolean> =
+ windowRootViewBlurInteractor.isBlurCurrentlySupported
}
@Module
object ActivatableNotificationViewModelModule {
@Provides
- fun provideViewModel(interactor: AccessibilityInteractor) =
- ActivatableNotificationViewModel(interactor)
+ fun provideViewModel(
+ a11yInteractor: AccessibilityInteractor,
+ windowRootViewBlurInteractor: WindowRootViewBlurInteractor
+ ) =
+ ActivatableNotificationViewModel(a11yInteractor, windowRootViewBlurInteractor)
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ui/viewmodel/BundleHeaderViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ui/viewmodel/BundleHeaderViewModel.kt
new file mode 100644
index 000000000000..d02ae43a98cf
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ui/viewmodel/BundleHeaderViewModel.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.statusbar.notification.row.ui.viewmodel
+
+import android.graphics.drawable.Drawable
+import android.view.View
+import androidx.compose.animation.core.tween
+import androidx.compose.material3.ExperimentalMaterial3ExpressiveApi
+import androidx.compose.material3.MotionScheme
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.setValue
+import androidx.compose.ui.unit.dp
+import com.android.compose.animation.scene.MutableSceneTransitionLayoutState
+import com.android.compose.animation.scene.SceneTransitionLayoutState
+import com.android.compose.animation.scene.transitions
+import com.android.systemui.notifications.ui.composable.row.BundleHeader
+import kotlinx.coroutines.CoroutineScope
+
+interface BundleHeaderViewModel {
+ val titleText: String
+ val numberOfChildren: Int?
+ val bundleIcon: Drawable?
+ val previewIcons: List<Drawable>
+
+ val state: SceneTransitionLayoutState
+
+ val hasUnreadMessages: Boolean
+ val backgroundDrawable: Drawable?
+
+ fun onHeaderClicked(scope: CoroutineScope)
+}
+
+class BundleHeaderViewModelImpl : BundleHeaderViewModel {
+ override var titleText by mutableStateOf("")
+ override var numberOfChildren by mutableStateOf<Int?>(1)
+ override var hasUnreadMessages by mutableStateOf(true)
+ override var bundleIcon by mutableStateOf<Drawable?>(null)
+ override var previewIcons by mutableStateOf(listOf<Drawable>())
+ override var backgroundDrawable by mutableStateOf<Drawable?>(null)
+
+ var onExpandClickListener: View.OnClickListener? = null
+
+ @OptIn(ExperimentalMaterial3ExpressiveApi::class)
+ override var state: MutableSceneTransitionLayoutState =
+ MutableSceneTransitionLayoutState(
+ BundleHeader.Scenes.Collapsed,
+ MotionScheme.standard(),
+ transitions {
+ from(BundleHeader.Scenes.Collapsed, to = BundleHeader.Scenes.Expanded) {
+ spec = tween(500)
+ translate(BundleHeader.Elements.PreviewIcon3, x = 32.dp)
+ translate(BundleHeader.Elements.PreviewIcon2, x = 16.dp)
+ fade(BundleHeader.Elements.PreviewIcon1)
+ fade(BundleHeader.Elements.PreviewIcon2)
+ fade(BundleHeader.Elements.PreviewIcon3)
+ }
+ },
+ )
+
+ override fun onHeaderClicked(scope: CoroutineScope) {
+ val targetScene =
+ when (state.currentScene) {
+ BundleHeader.Scenes.Collapsed -> BundleHeader.Scenes.Expanded
+ BundleHeader.Scenes.Expanded -> BundleHeader.Scenes.Collapsed
+ else -> error("Unknown Scene")
+ }
+ state.setTargetScene(targetScene, scope)
+
+ onExpandClickListener?.onClick(null)
+ hasUnreadMessages = false
+ }
+}
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..8176d2a1be44 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
@@ -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);
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/AnimationProperties.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/AnimationProperties.java
index 3d60092cf29a..4d01cbd19253 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/AnimationProperties.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/AnimationProperties.java
@@ -23,7 +23,7 @@ import android.util.Property;
import android.view.View;
import android.view.animation.Interpolator;
-import androidx.dynamicanimation.animation.DynamicAnimation;
+import com.android.internal.dynamicanimation.animation.DynamicAnimation;
import java.util.function.Consumer;
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 4390f1b16ffd..c2c271bd6e81 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;
}
@@ -1226,6 +1230,14 @@ public class NotificationStackScrollLayout
}
@Override
+ public void setOccluded(boolean isOccluded) {
+ if (SceneContainerFlag.isUnexpectedlyInLegacyMode()) {
+ return;
+ }
+ this.setVisibility(isOccluded ? View.INVISIBLE : View.VISIBLE);
+ }
+
+ @Override
public void setScrollState(@NonNull ShadeScrollState scrollState) {
if (SceneContainerFlag.isUnexpectedlyInLegacyMode()) {
return;
@@ -1278,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));
}
@@ -3066,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);
}
@@ -3365,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.
@@ -3425,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);
@@ -5060,10 +5078,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);
}
/**
@@ -5072,8 +5091,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()) {
@@ -5088,19 +5110,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);
@@ -5108,6 +5137,9 @@ public class NotificationStackScrollLayout
setHeadsUpAnimatingAway(true);
}
}
+ if (StatusBarNotifChips.isEnabled()) {
+ row.setHasStatusBarChipDuringHeadsUpAnimation(hasStatusBarChip);
+ }
requestChildrenUpdate();
}
}
@@ -6461,30 +6493,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()
@@ -6674,6 +6726,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..d23a4c6307fc 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
@@ -1059,7 +1059,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 9a5cf9ceed53..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
@@ -32,13 +32,13 @@ import android.content.Context;
import android.util.Property;
import android.view.View;
-import androidx.dynamicanimation.animation.DynamicAnimation;
-
import com.android.app.animation.Interpolators;
import com.android.internal.annotations.VisibleForTesting;
+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;
@@ -290,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;
@@ -303,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
@@ -553,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,
@@ -665,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;
}
@@ -736,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/ViewState.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ViewState.java
index 2ef6f362af34..29dbeb2c8ee4 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ViewState.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ViewState.java
@@ -30,10 +30,9 @@ import android.util.Property;
import android.view.View;
import android.view.animation.Interpolator;
-import androidx.dynamicanimation.animation.DynamicAnimation;
-import androidx.dynamicanimation.animation.SpringAnimation;
-
import com.android.app.animation.Interpolators;
+import com.android.internal.dynamicanimation.animation.DynamicAnimation;
+import com.android.internal.dynamicanimation.animation.SpringAnimation;
import com.android.systemui.Dumpable;
import com.android.systemui.res.R;
import com.android.systemui.statusbar.notification.AnimatableProperty;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/view/NotificationScrollView.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/view/NotificationScrollView.kt
index a7305f7f27ab..9c855e9cd9b7 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/view/NotificationScrollView.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/view/NotificationScrollView.kt
@@ -49,6 +49,9 @@ interface NotificationScrollView {
/** Max alpha for this view */
fun setMaxAlpha(alpha: Float)
+ /** Set whether this view is occluded by something else. */
+ fun setOccluded(isOccluded: Boolean)
+
/** Sets a clipping shape, which defines the drawable area of this view. */
fun setClippingShape(shape: ShadeScrimShape?)
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/viewbinder/NotificationScrollViewBinder.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/NotificationScrollViewBinder.kt
index a4e39cbd8388..653344ae9203 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/NotificationScrollViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/NotificationScrollViewBinder.kt
@@ -86,6 +86,8 @@ constructor(
.collectTraced { view.setClippingShape(it) }
}
+ launch { viewModel.isOccluded.collectTraced { view.setOccluded(it) } }
+
launch { viewModel.maxAlpha.collectTraced { view.setMaxAlpha(it) } }
launch { viewModel.shadeScrollState.collect { view.setScrollState(it) } }
launch {
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 1dbaf2f0f401..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
@@ -24,7 +24,9 @@ import com.android.compose.animation.scene.ObservableTransitionState
import com.android.compose.animation.scene.ObservableTransitionState.Idle
import com.android.compose.animation.scene.ObservableTransitionState.Transition
import com.android.compose.animation.scene.ObservableTransitionState.Transition.ChangeScene
+import com.android.compose.animation.scene.OverlayKey
import com.android.compose.animation.scene.SceneKey
+import com.android.systemui.bouncer.domain.interactor.BouncerInteractor
import com.android.systemui.dump.DumpManager
import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor
import com.android.systemui.lifecycle.ExclusiveActivatable
@@ -70,6 +72,7 @@ constructor(
private val stackAppearanceInteractor: NotificationStackAppearanceInteractor,
shadeInteractor: ShadeInteractor,
shadeModeInteractor: ShadeModeInteractor,
+ bouncerInteractor: BouncerInteractor,
private val remoteInputInteractor: RemoteInputInteractor,
private val sceneInteractor: SceneInteractor,
// TODO(b/336364825) Remove Lazy when SceneContainerFlag is released -
@@ -131,12 +134,15 @@ constructor(
private fun expandFractionDuringOverlayTransition(
transition: Transition,
currentScene: SceneKey,
+ currentOverlays: Set<OverlayKey>,
shadeExpansion: Float,
): Float {
return if (currentScene == Scenes.Lockscreen) {
1f
} else if (transition.isTransitioningFromOrTo(Overlays.NotificationsShade)) {
shadeExpansion
+ } else if (Overlays.NotificationsShade in currentOverlays) {
+ 1f
} else {
0f
}
@@ -161,12 +167,13 @@ constructor(
shadeInteractor.qsExpansion,
shadeModeInteractor.shadeMode,
sceneInteractor.transitionState,
- ) { shadeExpansion, qsExpansion, _, transitionState ->
+ sceneInteractor.currentOverlays,
+ ) { shadeExpansion, qsExpansion, _, transitionState, currentOverlays ->
when (transitionState) {
is Idle ->
if (
expandedInScene(transitionState.currentScene) ||
- Overlays.NotificationsShade in transitionState.currentOverlays
+ Overlays.NotificationsShade in currentOverlays
) {
1f
} else {
@@ -182,12 +189,14 @@ constructor(
expandFractionDuringOverlayTransition(
transition = transitionState,
currentScene = transitionState.currentScene,
+ currentOverlays = currentOverlays,
shadeExpansion = shadeExpansion,
)
is Transition.ReplaceOverlay ->
expandFractionDuringOverlayTransition(
transition = transitionState,
currentScene = transitionState.currentScene,
+ currentOverlays = currentOverlays,
shadeExpansion = shadeExpansion,
)
}
@@ -198,6 +207,12 @@ constructor(
val qsExpandFraction: Flow<Float> =
shadeInteractor.qsExpansion.dumpWhileCollecting("qsExpandFraction")
+ val isOccluded: Flow<Boolean> =
+ bouncerInteractor.bouncerExpansion
+ .map { it == 1f }
+ .distinctUntilChanged()
+ .dumpWhileCollecting("isOccluded")
+
/** Blur radius to be applied to Notifications. */
fun blurRadius(maxBlurRadius: Flow<Int>) =
combine(blurFraction, maxBlurRadius) { fraction, maxRadius -> fraction * maxRadius }
@@ -251,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/stack/ui/viewmodel/SharedNotificationContainerViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModel.kt
index 33cc62c9a75a..9d55e1d9d592 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModel.kt
@@ -21,6 +21,7 @@ import android.content.Context
import androidx.annotation.VisibleForTesting
import com.android.app.tracing.coroutines.flow.flowName
import com.android.systemui.Flags.glanceableHubV2
+import com.android.systemui.bouncer.domain.interactor.BouncerInteractor
import com.android.systemui.common.shared.model.NotificationContainerBounds
import com.android.systemui.common.ui.domain.interactor.ConfigurationInteractor
import com.android.systemui.communal.domain.interactor.CommunalSceneInteractor
@@ -106,7 +107,6 @@ import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.combineTransform
import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.emptyFlow
-import kotlinx.coroutines.flow.filter
import kotlinx.coroutines.flow.filterNotNull
import kotlinx.coroutines.flow.first
import kotlinx.coroutines.flow.flatMapLatest
@@ -132,6 +132,7 @@ constructor(
private val keyguardInteractor: KeyguardInteractor,
private val keyguardTransitionInteractor: KeyguardTransitionInteractor,
private val shadeInteractor: ShadeInteractor,
+ private val bouncerInteractor: BouncerInteractor,
shadeModeInteractor: ShadeModeInteractor,
notificationStackAppearanceInteractor: NotificationStackAppearanceInteractor,
private val alternateBouncerToGoneTransitionViewModel:
@@ -516,8 +517,13 @@ constructor(
combineTransform(
shadeInteractor.shadeExpansion,
shadeInteractor.qsExpansion,
- ) { shadeExpansion, qsExpansion ->
- if (qsExpansion == 1f) {
+ bouncerInteractor.bouncerExpansion,
+ ) { shadeExpansion, qsExpansion, bouncerExpansion ->
+ if (bouncerExpansion == 1f) {
+ emit(0f)
+ } else if (bouncerExpansion > 0f) {
+ emit(1 - bouncerExpansion)
+ } else if (qsExpansion == 1f) {
// Ensure HUNs will be visible in QS shade (at least while
// unlocked)
emit(1f)
@@ -526,19 +532,36 @@ constructor(
emit(1f - qsExpansion)
}
}
- Split -> isAnyExpanded.filter { it }.map { 1f }
+ Split ->
+ combineTransform(isAnyExpanded, bouncerInteractor.bouncerExpansion) {
+ isAnyExpanded,
+ bouncerExpansion ->
+ if (bouncerExpansion == 1f) {
+ emit(0f)
+ } else if (bouncerExpansion > 0f) {
+ emit(1 - bouncerExpansion)
+ } else if (isAnyExpanded) {
+ emit(1f)
+ }
+ }
Dual ->
combineTransform(
shadeModeInteractor.isShadeLayoutWide,
headsUpNotificationInteractor.get().isHeadsUpOrAnimatingAway,
shadeInteractor.shadeExpansion,
shadeInteractor.qsExpansion,
+ bouncerInteractor.bouncerExpansion,
) {
isShadeLayoutWide,
isHeadsUpOrAnimatingAway,
shadeExpansion,
- qsExpansion ->
- if (isShadeLayoutWide) {
+ qsExpansion,
+ bouncerExpansion ->
+ if (bouncerExpansion == 1f) {
+ emit(0f)
+ } else if (bouncerExpansion > 0f) {
+ emit(1 - bouncerExpansion)
+ } else if (isShadeLayoutWide) {
if (shadeExpansion > 0f) {
emit(1f)
}
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/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/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/dagger/StatusBarPipelineModule.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/dagger/StatusBarPipelineModule.kt
index c71162a22d2f..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
@@ -18,7 +18,9 @@ package com.android.systemui.statusbar.pipeline.dagger
import android.net.wifi.WifiManager
import com.android.systemui.CoreStartable
+import com.android.systemui.Flags
import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.kairos.ExperimentalKairosApi
import com.android.systemui.log.LogBuffer
import com.android.systemui.log.LogBufferFactory
import com.android.systemui.log.table.TableLogBuffer
@@ -35,9 +37,16 @@ import com.android.systemui.statusbar.pipeline.mobile.data.repository.CarrierCon
import com.android.systemui.statusbar.pipeline.mobile.data.repository.CarrierConfigRepository
import com.android.systemui.statusbar.pipeline.mobile.data.repository.CarrierConfigRepositoryImpl
import com.android.systemui.statusbar.pipeline.mobile.data.repository.MobileConnectionsRepository
+import com.android.systemui.statusbar.pipeline.mobile.data.repository.MobileConnectionsRepositoryKairosAdapter
import com.android.systemui.statusbar.pipeline.mobile.data.repository.MobileRepositorySwitcher
+import com.android.systemui.statusbar.pipeline.mobile.data.repository.MobileRepositorySwitcherKairos
+import com.android.systemui.statusbar.pipeline.mobile.data.repository.demo.DemoModeMobileConnectionDataSourceKairosImpl
+import com.android.systemui.statusbar.pipeline.mobile.data.repository.prod.MobileConnectionRepositoryKairosFactoryImpl
+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
import com.android.systemui.statusbar.pipeline.mobile.util.MobileMappingsProxy
@@ -74,9 +83,22 @@ import dagger.multibindings.ClassKey
import dagger.multibindings.IntoMap
import java.util.function.Supplier
import javax.inject.Named
+import javax.inject.Provider
import kotlinx.coroutines.flow.Flow
-@Module
+@OptIn(ExperimentalKairosApi::class)
+@Module(
+ includes =
+ [
+ DemoModeMobileConnectionDataSourceKairosImpl.Module::class,
+ MobileRepositorySwitcherKairos.Module::class,
+ MobileConnectionsRepositoryKairosImpl.Module::class,
+ MobileIconsInteractorKairosImpl.Module::class,
+ MobileConnectionRepositoryKairosFactoryImpl.Module::class,
+ MobileConnectionsRepositoryKairosAdapter.Module::class,
+ MobileIconsInteractorKairosAdapter.Module::class,
+ ]
+)
abstract class StatusBarPipelineModule {
@Binds
abstract fun airplaneModeRepository(impl: AirplaneModeRepositoryImpl): AirplaneModeRepository
@@ -117,11 +139,6 @@ abstract class StatusBarPipelineModule {
@Binds abstract fun wifiInteractor(impl: WifiInteractorImpl): WifiInteractor
- @Binds
- abstract fun mobileConnectionsRepository(
- impl: MobileRepositorySwitcher
- ): MobileConnectionsRepository
-
@Binds abstract fun userSetupRepository(impl: UserSetupRepositoryImpl): UserSetupRepository
@Binds abstract fun mobileMappingsProxy(impl: MobileMappingsProxyImpl): MobileMappingsProxy
@@ -135,9 +152,6 @@ abstract class StatusBarPipelineModule {
): SubscriptionManagerProxy
@Binds
- abstract fun mobileIconsInteractor(impl: MobileIconsInteractorImpl): MobileIconsInteractor
-
- @Binds
@IntoMap
@ClassKey(MobileUiAdapter::class)
abstract fun bindFeature(impl: MobileUiAdapter): CoreStartable
@@ -158,6 +172,30 @@ abstract class StatusBarPipelineModule {
companion object {
@Provides
+ fun mobileIconsInteractor(
+ impl: Provider<MobileIconsInteractorImpl>,
+ kairosImpl: Provider<MobileIconsInteractorKairosAdapter>,
+ ): MobileIconsInteractor {
+ return if (Flags.statusBarMobileIconKairos()) {
+ kairosImpl.get()
+ } else {
+ impl.get()
+ }
+ }
+
+ @Provides
+ fun mobileConnectionsRepository(
+ impl: Provider<MobileRepositorySwitcher>,
+ kairosImpl: Provider<MobileConnectionsRepositoryKairosAdapter>,
+ ): MobileConnectionsRepository {
+ return if (Flags.statusBarMobileIconKairos()) {
+ kairosImpl.get()
+ } else {
+ impl.get()
+ }
+ }
+
+ @Provides
@SysUISingleton
fun provideRealWifiRepository(
wifiManager: WifiManager?,
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/MobileConnectionRepositoryKairos.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileConnectionRepositoryKairos.kt
new file mode 100644
index 000000000000..2e796263afa9
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileConnectionRepositoryKairos.kt
@@ -0,0 +1,178 @@
+/*
+ * 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.data.repository
+
+import android.telephony.CellSignalStrength
+import android.telephony.TelephonyManager
+import com.android.systemui.kairos.ExperimentalKairosApi
+import com.android.systemui.kairos.State
+import com.android.systemui.log.table.TableLogBuffer
+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
+import com.android.systemui.statusbar.pipeline.shared.data.model.DataActivityModel
+
+/**
+ * Every mobile line of service can be identified via a [SubscriptionInfo] object. We set up a
+ * repository for each individual, tracked subscription via [MobileConnectionsRepository], and this
+ * repository is responsible for setting up a [TelephonyManager] object tied to its subscriptionId
+ *
+ * There should only ever be one [MobileConnectionRepository] per subscription, since
+ * [TelephonyManager] limits the number of callbacks that can be registered per process.
+ *
+ * This repository should have all of the relevant information for a single line of service, which
+ * eventually becomes a single icon in the status bar.
+ */
+@ExperimentalKairosApi
+interface MobileConnectionRepositoryKairos {
+ /** The subscriptionId that this connection represents */
+ val subId: Int
+
+ /** The carrierId for this connection. See [TelephonyManager.getSimCarrierId] */
+ val carrierId: State<Int>
+
+ /** Reflects the value from the carrier config INFLATE_SIGNAL_STRENGTH for this connection */
+ val inflateSignalStrength: State<Boolean>
+
+ /** Carrier config KEY_SHOW_5G_SLICE_ICON_BOOL for this connection */
+ val allowNetworkSliceIndicator: State<Boolean>
+
+ /**
+ * The table log buffer created for this connection. Will have the name "MobileConnectionLog
+ * [subId]"
+ */
+ val tableLogBuffer: TableLogBuffer
+
+ /** True if the [android.telephony.ServiceState] says this connection is emergency calls only */
+ val isEmergencyOnly: State<Boolean>
+
+ /** True if [android.telephony.ServiceState] says we are roaming */
+ val isRoaming: State<Boolean>
+
+ /**
+ * See [android.telephony.ServiceState.getOperatorAlphaShort], this value is defined as the
+ * current registered operator name in short alphanumeric format. In some cases this name might
+ * be preferred over other methods of calculating the network name
+ */
+ val operatorAlphaShort: State<String?>
+
+ /**
+ * TODO (b/263167683): Clarify this field
+ *
+ * This check comes from [com.android.settingslib.Utils.isInService]. It is intended to be a
+ * mapping from a ServiceState to a notion of connectivity. Notably, it will consider a
+ * connection to be in-service if either the voice registration state is IN_SERVICE or the data
+ * registration state is IN_SERVICE and NOT IWLAN.
+ */
+ val isInService: State<Boolean>
+
+ /**
+ * True if this subscription is actively connected to a non-terrestrial network and false
+ * otherwise. Reflects [android.telephony.ServiceState.isUsingNonTerrestrialNetwork].
+ *
+ * Notably: This value reflects that this subscription is **currently** using a non-terrestrial
+ * network, because some subscriptions can switch between terrestrial and non-terrestrial
+ * networks. [SubscriptionModel.isExclusivelyNonTerrestrial] reflects whether a subscription is
+ * configured to exclusively connect to non-terrestrial networks. [isNonTerrestrial] can change
+ * during the lifetime of a subscription but [SubscriptionModel.isExclusivelyNonTerrestrial]
+ * will stay constant.
+ */
+ val isNonTerrestrial: State<Boolean>
+
+ /** True if [android.telephony.SignalStrength] told us that this connection is using GSM */
+ val isGsm: State<Boolean>
+
+ /**
+ * There is still specific logic in the pipeline that calls out CDMA level explicitly. This
+ * field is not completely orthogonal to [primaryLevel], because CDMA could be primary.
+ */
+ // @IntRange(from = 0, to = 4)
+ val cdmaLevel: State<Int>
+
+ /** [android.telephony.SignalStrength]'s concept of the overall signal level */
+ // @IntRange(from = 0, to = 4)
+ val primaryLevel: State<Int>
+
+ /**
+ * This level can be used to reflect the signal strength when in carrier roaming NTN mode
+ * (carrier-based satellite)
+ */
+ val satelliteLevel: State<Int>
+
+ /** The current data connection state. See [DataConnectionState] */
+ val dataConnectionState: State<DataConnectionState>
+
+ /** The current data activity direction. See [DataActivityModel] */
+ val dataActivityDirection: State<DataActivityModel>
+
+ /** True if there is currently a carrier network change in process */
+ val carrierNetworkChangeActive: State<Boolean>
+
+ /**
+ * [resolvedNetworkType] is the [TelephonyDisplayInfo.getOverrideNetworkType] if it exists or
+ * [TelephonyDisplayInfo.getNetworkType]. This is used to look up the proper network type icon
+ */
+ val resolvedNetworkType: State<ResolvedNetworkType>
+
+ /** The total number of levels. Used with [SignalDrawable]. */
+ val numberOfLevels: State<Int>
+
+ /** Observable tracking [TelephonyManager.isDataConnectionAllowed] */
+ val dataEnabled: State<Boolean>
+
+ /**
+ * See [TelephonyManager.getCdmaEnhancedRoamingIndicatorDisplayNumber]. This bit only matters if
+ * the connection type is CDMA.
+ *
+ * True if the Enhanced Roaming Indicator (ERI) display number is not [TelephonyManager.ERI_OFF]
+ */
+ val cdmaRoaming: State<Boolean>
+
+ /** The service provider name for this network connection, or the default name. */
+ val networkName: State<NetworkNameModel>
+
+ /**
+ * The service provider name for this network connection, or the default name.
+ *
+ * TODO(b/296600321): De-duplicate this field with [networkName] after determining the data
+ * provided is identical
+ */
+ val carrierName: State<NetworkNameModel>
+
+ /**
+ * True if this type of connection is allowed while airplane mode is on, and false otherwise.
+ */
+ val isAllowedDuringAirplaneMode: State<Boolean>
+
+ /**
+ * True if this network has NET_CAPABILITIY_PRIORITIZE_LATENCY, and can be considered to be a
+ * network slice
+ */
+ val hasPrioritizedNetworkCapabilities: State<Boolean>
+
+ /**
+ * True if this connection is in emergency callback mode.
+ *
+ * @see [TelephonyManager.getEmergencyCallbackMode]
+ */
+ val isInEcmMode: State<Boolean>
+
+ companion object {
+ /** The default number of levels to use for [numberOfLevels]. */
+ val DEFAULT_NUM_LEVELS = CellSignalStrength.getNumSignalStrengthLevels()
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileConnectionsRepositoryKairos.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileConnectionsRepositoryKairos.kt
new file mode 100644
index 000000000000..79bfb6e48171
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileConnectionsRepositoryKairos.kt
@@ -0,0 +1,122 @@
+/*
+ * 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.data.repository
+
+import android.telephony.CarrierConfigManager
+import android.telephony.SubscriptionManager
+import com.android.settingslib.SignalIcon.MobileIconGroup
+import com.android.settingslib.mobile.MobileMappings
+import com.android.settingslib.mobile.MobileMappings.Config
+import com.android.systemui.kairos.Events
+import com.android.systemui.kairos.ExperimentalKairosApi
+import com.android.systemui.kairos.Incremental
+import com.android.systemui.kairos.State
+import com.android.systemui.statusbar.pipeline.mobile.data.model.SubscriptionModel
+
+/**
+ * Repo for monitoring the complete active subscription info list, to be consumed and filtered based
+ * on various policy
+ */
+@ExperimentalKairosApi
+interface MobileConnectionsRepositoryKairos {
+
+ /** All active mobile connections. */
+ val mobileConnectionsBySubId: Incremental<Int, MobileConnectionRepositoryKairos>
+
+ /** Observable list of current mobile subscriptions */
+ val subscriptions: State<Collection<SubscriptionModel>>
+
+ /**
+ * Observable for the subscriptionId of the current mobile data connection. Null if we don't
+ * have a valid subscription id
+ */
+ val activeMobileDataSubscriptionId: State<Int?>
+
+ /** Repo that tracks the current [activeMobileDataSubscriptionId] */
+ val activeMobileDataRepository: State<MobileConnectionRepositoryKairos?>
+
+ /**
+ * Observable event for when the active data sim switches but the group stays the same. E.g.,
+ * CBRS switching would trigger this
+ */
+ val activeSubChangedInGroupEvent: Events<Unit>
+
+ /** Tracks [SubscriptionManager.getDefaultDataSubscriptionId]. `null` if there is no default. */
+ val defaultDataSubId: State<Int?>
+
+ /**
+ * True if the default network connection is a mobile-like connection and false otherwise.
+ *
+ * This is typically shown by having [android.net.NetworkCapabilities.TRANSPORT_CELLULAR], but
+ * there are edge cases (like carrier merged wifi) that could also result in the default
+ * connection being mobile-like.
+ */
+ val mobileIsDefault: State<Boolean>
+
+ /**
+ * True if the device currently has a carrier merged connection.
+ *
+ * See [CarrierMergedConnectionRepository] for more info.
+ */
+ val hasCarrierMergedConnection: State<Boolean>
+
+ /** True if the default network connection is validated and false otherwise. */
+ val defaultConnectionIsValidated: State<Boolean>
+
+ /**
+ * [Config] is an object that tracks relevant configuration flags for a given subscription ID.
+ * In the case of [MobileMappings], it's hard-coded to check the default data subscription's
+ * config, so this will apply to every icon that we care about.
+ *
+ * Relevant bits in the config are things like
+ * [CarrierConfigManager.KEY_SHOW_4G_FOR_LTE_DATA_ICON_BOOL]
+ *
+ * This flow will produce whenever the default data subscription or the carrier config changes.
+ */
+ val defaultDataSubRatConfig: State<Config>
+
+ /** The icon mapping from network type to [MobileIconGroup] for the default subscription */
+ val defaultMobileIconMapping: State<Map<String, MobileIconGroup>>
+
+ /** Fallback [MobileIconGroup] in the case where there is no icon in the mapping */
+ val defaultMobileIconGroup: State<MobileIconGroup>
+
+ /**
+ * Can the device make emergency calls using the device-based service state? This field is only
+ * useful when all known active subscriptions are OOS and not emergency call capable.
+ *
+ * Specifically, this checks every [ServiceState] of the device, and looks for any that report
+ * [ServiceState.isEmergencyOnly].
+ *
+ * This is an eager flow, and re-evaluates whenever ACTION_SERVICE_STATE is sent for subId = -1.
+ */
+ val isDeviceEmergencyCallCapable: State<Boolean>
+
+ /**
+ * If any active SIM on the device is in
+ * [android.telephony.TelephonyManager.SIM_STATE_PIN_REQUIRED] or
+ * [android.telephony.TelephonyManager.SIM_STATE_PUK_REQUIRED] or
+ * [android.telephony.TelephonyManager.SIM_STATE_PERM_DISABLED]
+ */
+ val isAnySimSecure: State<Boolean>
+
+ /**
+ * Checks if any subscription has [android.telephony.TelephonyManager.getEmergencyCallbackMode]
+ * == true
+ */
+ val isInEcmMode: State<Boolean>
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileConnectionsRepositoryKairosAdapter.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileConnectionsRepositoryKairosAdapter.kt
new file mode 100644
index 000000000000..64144d9a0ab5
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileConnectionsRepositoryKairosAdapter.kt
@@ -0,0 +1,165 @@
+/*
+ * 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.data.repository
+
+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.prod.MobileConnectionRepositoryKairosAdapter
+import com.android.systemui.statusbar.pipeline.shared.data.repository.ConnectivityRepository
+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 MobileConnectionsRepositoryKairosAdapter
+@Inject
+constructor(
+ private val kairosRepo: MobileConnectionsRepositoryKairos,
+ private val kairosNetwork: KairosNetwork,
+ @Application scope: CoroutineScope,
+ connectivityRepository: ConnectivityRepository,
+ context: Context,
+ carrierConfigRepo: CarrierConfigRepository,
+) : MobileConnectionsRepository, KairosBuilder by kairosBuilder() {
+ override val subscriptions: StateFlow<List<SubscriptionModel>> =
+ kairosRepo.subscriptions
+ .map { it.toList() }
+ .toColdConflatedFlow(kairosNetwork)
+ .stateIn(scope, SharingStarted.WhileSubscribed(), emptyList())
+
+ override val activeMobileDataSubscriptionId: StateFlow<Int?> =
+ kairosRepo.activeMobileDataSubscriptionId
+ .toColdConflatedFlow(kairosNetwork)
+ .stateIn(scope, SharingStarted.WhileSubscribed(), null)
+
+ private val reposBySubIdK = buildIncremental {
+ kairosRepo.mobileConnectionsBySubId
+ .mapValues { (subId, repo) ->
+ buildSpec {
+ MobileConnectionRepositoryKairosAdapter(
+ kairosRepo = repo,
+ carrierConfig = carrierConfigRepo.getOrCreateConfigForSubId(subId),
+ )
+ }
+ }
+ .applyLatestSpecForKey()
+ }
+
+ private val reposBySubId =
+ reposBySubIdK
+ .toColdConflatedFlow(kairosNetwork)
+ .stateIn(scope, SharingStarted.Eagerly, emptyMap())
+
+ override val activeMobileDataRepository: StateFlow<MobileConnectionRepository?> =
+ combine(kairosRepo.activeMobileDataSubscriptionId, reposBySubIdK) { id, repos -> repos[id] }
+ .toColdConflatedFlow(kairosNetwork)
+ .stateIn(scope, SharingStarted.WhileSubscribed(), null)
+
+ override val activeSubChangedInGroupEvent: Flow<Unit> =
+ kairosRepo.activeSubChangedInGroupEvent.toColdConflatedFlow(kairosNetwork)
+
+ override val defaultDataSubId: StateFlow<Int?> =
+ kairosRepo.defaultDataSubId
+ .toColdConflatedFlow(kairosNetwork)
+ .stateIn(scope, SharingStarted.WhileSubscribed(), null)
+
+ override val mobileIsDefault: StateFlow<Boolean> =
+ kairosRepo.mobileIsDefault
+ .toColdConflatedFlow(kairosNetwork)
+ .stateIn(
+ scope,
+ SharingStarted.WhileSubscribed(),
+ connectivityRepository.defaultConnections.value.mobile.isDefault,
+ )
+
+ override val hasCarrierMergedConnection: Flow<Boolean> =
+ kairosRepo.hasCarrierMergedConnection.toColdConflatedFlow(kairosNetwork)
+
+ override val defaultConnectionIsValidated: StateFlow<Boolean> =
+ kairosRepo.defaultConnectionIsValidated
+ .toColdConflatedFlow(kairosNetwork)
+ .stateIn(
+ scope,
+ SharingStarted.WhileSubscribed(),
+ connectivityRepository.defaultConnections.value.isValidated,
+ )
+
+ override fun getRepoForSubId(subId: Int): MobileConnectionRepository =
+ reposBySubId.value[subId] ?: error("Unknown subscription id: $subId")
+
+ override val defaultDataSubRatConfig: StateFlow<MobileMappings.Config> =
+ kairosRepo.defaultDataSubRatConfig
+ .toColdConflatedFlow(kairosNetwork)
+ .stateIn(
+ scope,
+ SharingStarted.WhileSubscribed(),
+ MobileMappings.Config.readConfig(context),
+ )
+
+ override val defaultMobileIconMapping: Flow<Map<String, SignalIcon.MobileIconGroup>> =
+ kairosRepo.defaultMobileIconMapping.toColdConflatedFlow(kairosNetwork)
+
+ override val defaultMobileIconGroup: Flow<SignalIcon.MobileIconGroup> =
+ kairosRepo.defaultMobileIconGroup.toColdConflatedFlow(kairosNetwork)
+
+ override val isDeviceEmergencyCallCapable: StateFlow<Boolean> =
+ kairosRepo.isDeviceEmergencyCallCapable
+ .toColdConflatedFlow(kairosNetwork)
+ .stateIn(scope, SharingStarted.Eagerly, false)
+
+ override val isAnySimSecure: StateFlow<Boolean> =
+ kairosRepo.isAnySimSecure
+ .toColdConflatedFlow(kairosNetwork)
+ .stateIn(scope, SharingStarted.Eagerly, false)
+
+ override fun getIsAnySimSecure(): Boolean = isAnySimSecure.value
+
+ override suspend fun isInEcmMode(): Boolean =
+ kairosNetwork.transact { kairosRepo.isInEcmMode.sample() }
+
+ @dagger.Module
+ object Module {
+ @Provides
+ @ElementsIntoSet
+ fun kairosActivatable(
+ impl: Provider<MobileConnectionsRepositoryKairosAdapter>
+ ): Set<@JvmSuppressWildcards KairosActivatable> =
+ if (Flags.statusBarMobileIconKairos()) setOf(impl.get()) else emptySet()
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileRepositorySwitcher.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileRepositorySwitcher.kt
index 66587c779fbe..caf4bf502c06 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileRepositorySwitcher.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileRepositorySwitcher.kt
@@ -48,9 +48,9 @@ import kotlinx.coroutines.flow.stateIn
* something like this:
* ```
* RealRepository
- * │
- * ├──►RepositorySwitcher──►RealInteractor──►RealViewModel
- * │
+ * │
+ * ├──►RepositorySwitcher──►RealInteractor──►RealViewModel
+ * │
* DemoRepository
* ```
*
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
new file mode 100644
index 000000000000..1f5b849c56cc
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileRepositorySwitcherKairos.kt
@@ -0,0 +1,171 @@
+/*
+ * 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.data.repository
+
+import android.os.Bundle
+import androidx.annotation.VisibleForTesting
+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.activated
+import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.demomode.DemoMode
+import com.android.systemui.demomode.DemoModeController
+import com.android.systemui.kairos.Events
+import com.android.systemui.kairos.ExperimentalKairosApi
+import com.android.systemui.kairos.Incremental
+import com.android.systemui.kairos.State
+import com.android.systemui.kairos.flatMap
+import com.android.systemui.kairos.map
+import com.android.systemui.kairos.switchEvents
+import com.android.systemui.kairos.switchIncremental
+import com.android.systemui.kairosBuilder
+import com.android.systemui.statusbar.pipeline.mobile.data.model.SubscriptionModel
+import com.android.systemui.statusbar.pipeline.mobile.data.repository.demo.DemoMobileConnectionsRepositoryKairos
+import com.android.systemui.statusbar.pipeline.mobile.data.repository.prod.MobileConnectionsRepositoryKairosImpl
+import dagger.Binds
+import dagger.Provides
+import dagger.multibindings.ElementsIntoSet
+import javax.inject.Inject
+import javax.inject.Provider
+import kotlinx.coroutines.channels.awaitClose
+
+/**
+ * A provider for the [MobileConnectionsRepository] interface that can choose between the Demo and
+ * Prod concrete implementations at runtime. It works by defining a base flow, [activeRepo], which
+ * switches based on the latest information from [DemoModeController], and switches every flow in
+ * the interface to point to the currently-active provider. This allows us to put the demo mode
+ * interface in its own repository, completely separate from the real version, while still using all
+ * of the prod implementations for the rest of the pipeline (interactors and onward). Looks
+ * something like this:
+ * ```
+ * RealRepository
+ * │
+ * ├──►RepositorySwitcher──►RealInteractor──►RealViewModel
+ * │
+ * DemoRepository
+ * ```
+ *
+ * NOTE: because the UI layer for mobile icons relies on a nested-repository structure, it is likely
+ * that we will have to drain the subscription list whenever demo mode changes. Otherwise if a real
+ * subscription list [1] is replaced with a demo subscription list [1], the view models will not see
+ * a change (due to `distinctUntilChanged`) and will not refresh their data providers to the demo
+ * implementation.
+ */
+@ExperimentalKairosApi
+@SysUISingleton
+class MobileRepositorySwitcherKairos
+@Inject
+constructor(
+ private val realRepository: MobileConnectionsRepositoryKairosImpl,
+ private val demoRepositoryFactory: DemoMobileConnectionsRepositoryKairos.Factory,
+ demoModeController: DemoModeController,
+) : MobileConnectionsRepositoryKairos, KairosBuilder by kairosBuilder() {
+
+ private val isDemoMode: State<Boolean> = buildState {
+ conflatedCallbackFlow {
+ val callback =
+ object : DemoMode {
+ override fun dispatchDemoCommand(command: String?, args: Bundle?) {
+ // Nothing, we just care about on/off
+ }
+
+ override fun onDemoModeStarted() {
+ trySend(true)
+ }
+
+ override fun onDemoModeFinished() {
+ trySend(false)
+ }
+ }
+
+ demoModeController.addCallback(callback)
+ awaitClose { demoModeController.removeCallback(callback) }
+ }
+ .toState(demoModeController.isInDemoMode)
+ }
+
+ // Convenient definition flow for the currently active repo (based on demo mode or not)
+ @VisibleForTesting
+ val activeRepo: State<MobileConnectionsRepositoryKairos> = buildState {
+ isDemoMode.mapLatestBuild { demoMode ->
+ if (demoMode) {
+ activated { demoRepositoryFactory.create() }
+ } else {
+ realRepository
+ }
+ }
+ }
+
+ override val mobileConnectionsBySubId: Incremental<Int, MobileConnectionRepositoryKairos> =
+ activeRepo.map { it.mobileConnectionsBySubId }.switchIncremental()
+
+ override val subscriptions: State<Collection<SubscriptionModel>> =
+ activeRepo.flatMap { it.subscriptions }
+
+ override val activeMobileDataSubscriptionId: State<Int?> =
+ activeRepo.flatMap { it.activeMobileDataSubscriptionId }
+
+ override val activeMobileDataRepository: State<MobileConnectionRepositoryKairos?> =
+ activeRepo.flatMap { it.activeMobileDataRepository }
+
+ override val activeSubChangedInGroupEvent: Events<Unit> =
+ activeRepo.map { it.activeSubChangedInGroupEvent }.switchEvents()
+
+ override val defaultDataSubRatConfig: State<MobileMappings.Config> =
+ activeRepo.flatMap { it.defaultDataSubRatConfig }
+
+ override val defaultMobileIconMapping: State<Map<String, SignalIcon.MobileIconGroup>> =
+ activeRepo.flatMap { it.defaultMobileIconMapping }
+
+ override val defaultMobileIconGroup: State<SignalIcon.MobileIconGroup> =
+ activeRepo.flatMap { it.defaultMobileIconGroup }
+
+ override val isDeviceEmergencyCallCapable: State<Boolean> =
+ activeRepo.flatMap { it.isDeviceEmergencyCallCapable }
+
+ override val isAnySimSecure: State<Boolean> = activeRepo.flatMap { it.isAnySimSecure }
+
+ override val defaultDataSubId: State<Int?> = activeRepo.flatMap { it.defaultDataSubId }
+
+ override val mobileIsDefault: State<Boolean> = activeRepo.flatMap { it.mobileIsDefault }
+
+ override val hasCarrierMergedConnection: State<Boolean> =
+ activeRepo.flatMap { it.hasCarrierMergedConnection }
+
+ override val defaultConnectionIsValidated: State<Boolean> =
+ activeRepo.flatMap { it.defaultConnectionIsValidated }
+
+ override val isInEcmMode: State<Boolean> = activeRepo.flatMap { it.isInEcmMode }
+
+ @dagger.Module
+ interface Module {
+ @Binds fun bindImpl(impl: MobileRepositorySwitcherKairos): MobileConnectionsRepositoryKairos
+
+ companion object {
+ @Provides
+ @ElementsIntoSet
+ fun kairosActivatable(
+ impl: Provider<MobileRepositorySwitcherKairos>
+ ): Set<@JvmSuppressWildcards KairosActivatable> =
+ if (Flags.statusBarMobileIconKairos()) setOf(impl.get()) else emptySet()
+ }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/demo/DemoMobileConnectionRepositoryKairos.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/demo/DemoMobileConnectionRepositoryKairos.kt
new file mode 100644
index 000000000000..a244feb1739a
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/demo/DemoMobileConnectionRepositoryKairos.kt
@@ -0,0 +1,270 @@
+/*
+ * 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.data.repository.demo
+
+import android.telephony.SubscriptionManager.INVALID_SUBSCRIPTION_ID
+import android.telephony.TelephonyManager
+import com.android.settingslib.SignalIcon
+import com.android.systemui.KairosBuilder
+import com.android.systemui.kairos.Events
+import com.android.systemui.kairos.ExperimentalKairosApi
+import com.android.systemui.kairos.State
+import com.android.systemui.kairos.TransactionScope
+import com.android.systemui.kairos.map
+import com.android.systemui.kairos.mapCheap
+import com.android.systemui.kairos.mergeLeft
+import com.android.systemui.kairos.stateOf
+import com.android.systemui.kairos.util.Either
+import com.android.systemui.kairos.util.Either.First
+import com.android.systemui.kairos.util.Either.Second
+import com.android.systemui.kairos.util.firstOrNull
+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
+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.model.ResolvedNetworkType.DefaultNetworkType
+import com.android.systemui.statusbar.pipeline.mobile.data.repository.MobileConnectionRepository
+import com.android.systemui.statusbar.pipeline.mobile.data.repository.MobileConnectionRepository.Companion.DEFAULT_NUM_LEVELS
+import com.android.systemui.statusbar.pipeline.mobile.data.repository.MobileConnectionRepositoryKairos
+import com.android.systemui.statusbar.pipeline.mobile.data.repository.demo.model.FakeNetworkEventModel.Mobile as FakeMobileEvent
+import com.android.systemui.statusbar.pipeline.mobile.data.repository.prod.FullMobileConnectionRepositoryKairos.Companion.COL_CARRIER_ID
+import com.android.systemui.statusbar.pipeline.mobile.data.repository.prod.FullMobileConnectionRepositoryKairos.Companion.COL_CARRIER_NETWORK_CHANGE
+import com.android.systemui.statusbar.pipeline.mobile.data.repository.prod.FullMobileConnectionRepositoryKairos.Companion.COL_CDMA_LEVEL
+import com.android.systemui.statusbar.pipeline.mobile.data.repository.prod.FullMobileConnectionRepositoryKairos.Companion.COL_IS_IN_SERVICE
+import com.android.systemui.statusbar.pipeline.mobile.data.repository.prod.FullMobileConnectionRepositoryKairos.Companion.COL_IS_NTN
+import com.android.systemui.statusbar.pipeline.mobile.data.repository.prod.FullMobileConnectionRepositoryKairos.Companion.COL_OPERATOR
+import com.android.systemui.statusbar.pipeline.mobile.data.repository.prod.FullMobileConnectionRepositoryKairos.Companion.COL_PRIMARY_LEVEL
+import com.android.systemui.statusbar.pipeline.mobile.data.repository.prod.FullMobileConnectionRepositoryKairos.Companion.COL_ROAMING
+import com.android.systemui.statusbar.pipeline.mobile.data.repository.prod.FullMobileConnectionRepositoryKairos.Companion.COL_SATELLITE_LEVEL
+import com.android.systemui.statusbar.pipeline.shared.data.model.DataActivityModel
+import com.android.systemui.statusbar.pipeline.shared.data.model.toMobileDataActivityModel
+import com.android.systemui.statusbar.pipeline.wifi.data.repository.demo.model.FakeWifiEventModel.CarrierMerged as FakeCarrierMergedEvent
+import kotlinx.coroutines.flow.MutableStateFlow
+
+/**
+ * Demo version of [MobileConnectionRepository]. Note that this class shares all of its flows using
+ * [SharingStarted.WhileSubscribed()] to give the same semantics as using a regular
+ * [MutableStateFlow] while still logging all of the inputs in the same manor as the production
+ * repos.
+ */
+@ExperimentalKairosApi
+class DemoMobileConnectionRepositoryKairos(
+ override val subId: Int,
+ override val tableLogBuffer: TableLogBuffer,
+ mobileEvents: Events<FakeMobileEvent>,
+ carrierMergedResetEvents: Events<Any?>,
+ wifiEvents: Events<FakeCarrierMergedEvent>,
+ private val mobileMappingsReverseLookup: State<Map<SignalIcon.MobileIconGroup, String>>,
+) : MobileConnectionRepositoryKairos, KairosBuilder by kairosBuilder() {
+
+ private val initialState =
+ FakeMobileEvent(
+ level = null,
+ dataType = null,
+ subId = subId,
+ carrierId = null,
+ activity = null,
+ carrierNetworkChange = false,
+ roaming = false,
+ name = DEMO_CARRIER_NAME,
+ )
+
+ private val lastMobileEvent: State<FakeMobileEvent> = buildState {
+ mobileEvents.holdState(initialState)
+ }
+
+ private val lastEvent: State<Either<FakeMobileEvent, FakeCarrierMergedEvent>> = buildState {
+ mergeLeft(
+ mobileEvents.mapCheap { First(it) },
+ wifiEvents.mapCheap { Second(it) },
+ carrierMergedResetEvents.mapCheap { First(lastMobileEvent.sample()) },
+ )
+ .holdState(First(initialState))
+ }
+
+ override val carrierId: State<Int> =
+ lastEvent
+ .map { it.firstOrNull()?.carrierId ?: INVALID_SUBSCRIPTION_ID }
+ .also {
+ onActivated {
+ logDiffsForTable(
+ intState = it,
+ tableLogBuffer = tableLogBuffer,
+ columnName = COL_CARRIER_ID,
+ )
+ }
+ }
+
+ override val inflateSignalStrength: State<Boolean> = buildState {
+ mobileEvents
+ .map { ev -> ev.inflateStrength }
+ .holdState(false)
+ .also { logDiffsForTable(it, tableLogBuffer, "", columnName = "inflate") }
+ }
+
+ // I don't see a reason why we would turn the config off for demo mode.
+ override val allowNetworkSliceIndicator: State<Boolean> = stateOf(true)
+
+ // TODO(b/261029387): not yet supported
+ override val isEmergencyOnly: State<Boolean> = stateOf(false)
+
+ override val isRoaming: State<Boolean> =
+ lastEvent
+ .map { it.firstOrNull()?.roaming ?: false }
+ .also { onActivated { logDiffsForTable(it, tableLogBuffer, columnName = COL_ROAMING) } }
+
+ override val operatorAlphaShort: State<String?> =
+ lastEvent
+ .map { it.firstOrNull()?.name }
+ .also {
+ onActivated { logDiffsForTable(it, tableLogBuffer, columnName = COL_OPERATOR) }
+ }
+
+ override val isInService: State<Boolean> =
+ lastEvent
+ .map {
+ when (it) {
+ is First -> it.value.level?.let { level -> level > 0 } ?: false
+ is Second -> true
+ }
+ }
+ .also {
+ onActivated { logDiffsForTable(it, tableLogBuffer, columnName = COL_IS_IN_SERVICE) }
+ }
+
+ override val isNonTerrestrial: State<Boolean> = buildState {
+ mobileEvents
+ .map { it.ntn }
+ .holdState(false)
+ .also { logDiffsForTable(it, tableLogBuffer, columnName = COL_IS_NTN) }
+ }
+
+ // TODO(b/261029387): not yet supported
+ override val isGsm: State<Boolean> = stateOf(false)
+
+ override val cdmaLevel: State<Int> =
+ lastEvent
+ .map {
+ when (it) {
+ is First -> it.value.level ?: 0
+ is Second -> it.value.level
+ }
+ }
+ .also {
+ onActivated { logDiffsForTable(it, tableLogBuffer, columnName = COL_CDMA_LEVEL) }
+ }
+
+ override val primaryLevel: State<Int> =
+ lastEvent
+ .map {
+ when (it) {
+ is First -> it.value.level ?: 0
+ is Second -> it.value.level
+ }
+ }
+ .also {
+ onActivated { logDiffsForTable(it, tableLogBuffer, columnName = COL_PRIMARY_LEVEL) }
+ }
+
+ override val satelliteLevel: State<Int> =
+ stateOf(0).also {
+ onActivated { logDiffsForTable(it, tableLogBuffer, columnName = COL_SATELLITE_LEVEL) }
+ }
+
+ // TODO(b/261029387): not yet supported
+ override val dataConnectionState: State<DataConnectionState> =
+ buildState {
+ mergeLeft(mobileEvents, wifiEvents)
+ .map { DataConnectionState.Connected }
+ .holdState(DataConnectionState.Disconnected)
+ }
+ .also {
+ onActivated {
+ logDiffsForTable(diffableState = it, tableLogBuffer = tableLogBuffer)
+ }
+ }
+
+ override val dataActivityDirection: State<DataActivityModel> =
+ lastEvent
+ .map {
+ val activity =
+ when (it) {
+ is First -> it.value.activity ?: TelephonyManager.DATA_ACTIVITY_NONE
+ is Second -> it.value.activity
+ }
+ activity.toMobileDataActivityModel()
+ }
+ .also { onActivated { logDiffsForTable(it, tableLogBuffer, columnPrefix = "") } }
+
+ override val carrierNetworkChangeActive: State<Boolean> =
+ lastEvent
+ .map { it.firstOrNull()?.carrierNetworkChange ?: false }
+ .also {
+ onActivated {
+ logDiffsForTable(it, tableLogBuffer, columnName = COL_CARRIER_NETWORK_CHANGE)
+ }
+ }
+
+ override val resolvedNetworkType: State<ResolvedNetworkType> = buildState {
+ lastEvent
+ .mapTransactionally {
+ it.firstOrNull()?.dataType?.let { resolvedNetworkTypeForIconGroup(it) }
+ ?: ResolvedNetworkType.CarrierMergedNetworkType
+ }
+ .also { logDiffsForTable(it, tableLogBuffer, columnPrefix = "") }
+ }
+
+ override val numberOfLevels: State<Int> =
+ inflateSignalStrength.map { shouldInflate ->
+ if (shouldInflate) DEFAULT_NUM_LEVELS + 1 else DEFAULT_NUM_LEVELS
+ }
+
+ override val dataEnabled: State<Boolean> = stateOf(true)
+
+ override val cdmaRoaming: State<Boolean> = lastEvent.map { it.firstOrNull()?.roaming ?: false }
+
+ override val networkName: State<NetworkNameModel.IntentDerived> =
+ lastEvent.map {
+ NetworkNameModel.IntentDerived(it.firstOrNull()?.name ?: CARRIER_MERGED_NAME)
+ }
+
+ override val carrierName: State<NetworkNameModel.SubscriptionDerived> =
+ lastEvent.map {
+ NetworkNameModel.SubscriptionDerived(
+ it.firstOrNull()?.let { event -> "${event.name} ${event.subId}" }
+ ?: CARRIER_MERGED_NAME
+ )
+ }
+
+ override val isAllowedDuringAirplaneMode: State<Boolean> = lastEvent.map { it is Second }
+
+ override val hasPrioritizedNetworkCapabilities: State<Boolean> =
+ lastEvent.map { it.firstOrNull()?.slice ?: false }
+
+ override val isInEcmMode: State<Boolean> = stateOf(false)
+
+ private fun TransactionScope.resolvedNetworkTypeForIconGroup(
+ iconGroup: SignalIcon.MobileIconGroup?
+ ) = DefaultNetworkType(mobileMappingsReverseLookup.sample()[iconGroup] ?: "dis")
+
+ companion object {
+ private const val DEMO_CARRIER_NAME = "Demo Carrier"
+ private const val CARRIER_MERGED_NAME = "Carrier Merged Network"
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/demo/DemoMobileConnectionsRepositoryKairos.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/demo/DemoMobileConnectionsRepositoryKairos.kt
new file mode 100644
index 000000000000..925ee541bf73
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/demo/DemoMobileConnectionsRepositoryKairos.kt
@@ -0,0 +1,255 @@
+/*
+ * 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.data.repository.demo
+
+import android.content.Context
+import android.telephony.SubscriptionManager.INVALID_SUBSCRIPTION_ID
+import android.telephony.SubscriptionManager.PROFILE_CLASS_UNSET
+import android.util.Log
+import com.android.settingslib.SignalIcon
+import com.android.settingslib.mobile.MobileMappings
+import com.android.settingslib.mobile.TelephonyIcons
+import com.android.systemui.KairosBuilder
+import com.android.systemui.activated
+import com.android.systemui.kairos.BuildScope
+import com.android.systemui.kairos.Events
+import com.android.systemui.kairos.ExperimentalKairosApi
+import com.android.systemui.kairos.GroupedEvents
+import com.android.systemui.kairos.Incremental
+import com.android.systemui.kairos.State
+import com.android.systemui.kairos.TransactionScope
+import com.android.systemui.kairos.asIncremental
+import com.android.systemui.kairos.buildSpec
+import com.android.systemui.kairos.combine
+import com.android.systemui.kairos.emptyEvents
+import com.android.systemui.kairos.filter
+import com.android.systemui.kairos.filterIsInstance
+import com.android.systemui.kairos.groupBy
+import com.android.systemui.kairos.groupByKey
+import com.android.systemui.kairos.map
+import com.android.systemui.kairos.mapCheap
+import com.android.systemui.kairos.mapNotNull
+import com.android.systemui.kairos.mapValues
+import com.android.systemui.kairos.mergeLeft
+import com.android.systemui.kairos.stateOf
+import com.android.systemui.kairosBuilder
+import com.android.systemui.log.table.TableLogBufferFactory
+import com.android.systemui.statusbar.pipeline.mobile.data.model.SubscriptionModel
+import com.android.systemui.statusbar.pipeline.mobile.data.repository.MobileConnectionsRepositoryKairos
+import com.android.systemui.statusbar.pipeline.mobile.data.repository.demo.model.FakeNetworkEventModel
+import com.android.systemui.statusbar.pipeline.mobile.data.repository.demo.model.FakeNetworkEventModel.Mobile
+import com.android.systemui.statusbar.pipeline.mobile.data.repository.demo.model.FakeNetworkEventModel.MobileDisabled
+import com.android.systemui.statusbar.pipeline.mobile.data.repository.prod.FullMobileConnectionRepository.Factory.Companion.MOBILE_CONNECTION_BUFFER_SIZE
+import com.android.systemui.statusbar.pipeline.wifi.data.repository.demo.DemoModeWifiDataSource
+import com.android.systemui.statusbar.pipeline.wifi.data.repository.demo.model.FakeWifiEventModel
+import dagger.assisted.AssistedFactory
+import dagger.assisted.AssistedInject
+
+/** This repository vends out data based on demo mode commands */
+@ExperimentalKairosApi
+class DemoMobileConnectionsRepositoryKairos
+@AssistedInject
+constructor(
+ mobileDataSource: DemoModeMobileConnectionDataSourceKairos,
+ private val wifiDataSource: DemoModeWifiDataSource,
+ context: Context,
+ private val logFactory: TableLogBufferFactory,
+) : MobileConnectionsRepositoryKairos, KairosBuilder by kairosBuilder() {
+
+ @AssistedFactory
+ fun interface Factory {
+ fun create(): DemoMobileConnectionsRepositoryKairos
+ }
+
+ private val wifiEvents: Events<FakeWifiEventModel?> = buildEvents {
+ wifiDataSource.wifiEvents.toEvents()
+ }
+
+ private val mobileEventsWithSubId: Events<Pair<Int, FakeNetworkEventModel>> =
+ mobileDataSource.mobileEvents.mapNotNull { event ->
+ event?.let { (event.subId ?: lastSeenSubId.sample())?.let { it to event } }
+ }
+
+ private val mobileEventsBySubId: GroupedEvents<Int, FakeNetworkEventModel> =
+ mobileEventsWithSubId.map { mapOf(it) }.groupByKey()
+
+ private val carrierMergedEvents: Events<FakeWifiEventModel.CarrierMerged> =
+ wifiEvents.filterIsInstance<FakeWifiEventModel.CarrierMerged>()
+
+ private val wifiEventsBySubId: GroupedEvents<Int, FakeWifiEventModel.CarrierMerged> =
+ carrierMergedEvents.groupBy { it.subscriptionId }
+
+ private val lastSeenSubId: State<Int?> = buildState {
+ mergeLeft(
+ mobileEventsWithSubId.mapCheap { it.first },
+ carrierMergedEvents.mapCheap { it.subscriptionId },
+ )
+ .holdState(null)
+ }
+
+ private val activeCarrierMergedSubscription: State<Int?> = buildState {
+ mergeLeft(
+ carrierMergedEvents.mapCheap { it.subscriptionId },
+ wifiEvents
+ .filter {
+ it is FakeWifiEventModel.Wifi || it is FakeWifiEventModel.WifiDisabled
+ }
+ .map { null },
+ )
+ .holdState(null)
+ }
+
+ private val activeMobileSubscriptions: State<Set<Int>> = buildState {
+ mobileDataSource.mobileEvents
+ .mapNotNull { event ->
+ when (event) {
+ null -> null
+ is Mobile -> event.subId?.let { subId -> { subs: Set<Int> -> subs + subId } }
+ is MobileDisabled ->
+ (event.subId ?: maybeGetOnlySubIdForRemoval())?.let { subId ->
+ { subs: Set<Int> -> subs - subId }
+ }
+ }
+ }
+ .foldState(emptySet()) { f, s -> f(s) }
+ }
+
+ private val subscriptionIds: State<Set<Int>> =
+ combine(activeMobileSubscriptions, activeCarrierMergedSubscription) { mobile, carrierMerged
+ ->
+ carrierMerged?.let { mobile + carrierMerged } ?: mobile
+ }
+
+ private val subscriptionsById: State<Map<Int, SubscriptionModel>> =
+ subscriptionIds.map { subs ->
+ subs.associateWith { subId ->
+ SubscriptionModel(
+ subscriptionId = subId,
+ isOpportunistic = false,
+ carrierName = DEFAULT_CARRIER_NAME,
+ profileClass = PROFILE_CLASS_UNSET,
+ )
+ }
+ }
+
+ override val subscriptions: State<Collection<SubscriptionModel>> =
+ subscriptionsById.map { it.values }
+
+ private fun TransactionScope.maybeGetOnlySubIdForRemoval(): Int? {
+ val subIds = activeMobileSubscriptions.sample()
+ return if (subIds.size == 1) {
+ subIds.first()
+ } else {
+ Log.d(
+ TAG,
+ "processDisabledMobileState: Unable to infer subscription to " +
+ "disable. Specify subId using '-e slot <subId>'. " +
+ "Known subIds: [${subIds.joinToString(",")}]",
+ )
+ null
+ }
+ }
+
+ private val reposBySubId: Incremental<Int, DemoMobileConnectionRepositoryKairos> =
+ buildIncremental {
+ subscriptionsById
+ .asIncremental()
+ .mapValues { (id, _) -> buildSpec { newRepo(id) } }
+ .applyLatestSpecForKey()
+ }
+
+ // TODO(b/261029387): add a command for this value
+ override val activeMobileDataSubscriptionId: State<Int> =
+ // For now, active is just the first in the list
+ subscriptions.map { infos ->
+ infos.firstOrNull()?.subscriptionId ?: INVALID_SUBSCRIPTION_ID
+ }
+
+ override val activeMobileDataRepository: State<DemoMobileConnectionRepositoryKairos?> =
+ combine(activeMobileDataSubscriptionId, reposBySubId) { subId, repoMap -> repoMap[subId] }
+
+ // TODO(b/261029387): consider adding a demo command for this
+ override val activeSubChangedInGroupEvent: Events<Unit> = emptyEvents
+
+ /** Demo mode doesn't currently support modifications to the mobile mappings */
+ override val defaultDataSubRatConfig: State<MobileMappings.Config> =
+ stateOf(MobileMappings.Config.readConfig(context))
+
+ override val defaultMobileIconGroup: State<SignalIcon.MobileIconGroup> =
+ stateOf(TelephonyIcons.THREE_G)
+
+ // TODO(b/339023069): demo command for device-based emergency calls state
+ override val isDeviceEmergencyCallCapable: State<Boolean> = stateOf(false)
+
+ override val isAnySimSecure: State<Boolean> = stateOf(false)
+
+ override val defaultMobileIconMapping: State<Map<String, SignalIcon.MobileIconGroup>> =
+ stateOf(TelephonyIcons.ICON_NAME_TO_ICON)
+
+ /**
+ * In order to maintain compatibility with the old demo mode shell command API, reverse the
+ * [MobileMappings] lookup from (NetworkType: String -> Icon: MobileIconGroup), so that we can
+ * parse the string from the command line into a preferred icon group, and send _a_ valid
+ * network type for that icon through the pipeline.
+ *
+ * Note: collisions don't matter here, because the data source (the command line) only cares
+ * about the resulting icon, not the underlying network type.
+ */
+ private val mobileMappingsReverseLookup: State<Map<SignalIcon.MobileIconGroup, String>> =
+ defaultMobileIconMapping.map { networkToIconMap -> networkToIconMap.reverse() }
+
+ private fun <K, V> Map<K, V>.reverse() = entries.associate { (k, v) -> v to k }
+
+ // TODO(b/261029387): add a command for this value
+ override val defaultDataSubId: State<Int?> = stateOf(null)
+
+ // TODO(b/261029387): not yet supported
+ override val mobileIsDefault: State<Boolean> = stateOf(true)
+
+ // TODO(b/261029387): not yet supported
+ override val hasCarrierMergedConnection: State<Boolean> = stateOf(false)
+
+ // TODO(b/261029387): not yet supported
+ override val defaultConnectionIsValidated: State<Boolean> = stateOf(true)
+
+ override val isInEcmMode: State<Boolean> = stateOf(false)
+
+ override val mobileConnectionsBySubId: Incremental<Int, DemoMobileConnectionRepositoryKairos>
+ get() = reposBySubId
+
+ private fun BuildScope.newRepo(subId: Int) = activated {
+ DemoMobileConnectionRepositoryKairos(
+ subId = subId,
+ tableLogBuffer =
+ logFactory.getOrCreate(
+ "DemoMobileConnectionLog[$subId]",
+ MOBILE_CONNECTION_BUFFER_SIZE,
+ ),
+ mobileEvents = mobileEventsBySubId[subId].filterIsInstance(),
+ carrierMergedResetEvents =
+ wifiEvents.mapNotNull { it?.takeIf { it !is FakeWifiEventModel.CarrierMerged } },
+ wifiEvents = wifiEventsBySubId[subId],
+ mobileMappingsReverseLookup = mobileMappingsReverseLookup,
+ )
+ }
+
+ companion object {
+ private const val TAG = "DemoMobileConnectionsRepo"
+
+ private const val DEFAULT_CARRIER_NAME = "demo carrier"
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/demo/DemoModeMobileConnectionDataSourceKairos.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/demo/DemoModeMobileConnectionDataSourceKairos.kt
new file mode 100644
index 000000000000..f32938335e6d
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/demo/DemoModeMobileConnectionDataSourceKairos.kt
@@ -0,0 +1,157 @@
+/*
+ * 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.data.repository.demo
+
+import android.os.Bundle
+import android.telephony.Annotation.DataActivityType
+import android.telephony.TelephonyManager.DATA_ACTIVITY_IN
+import android.telephony.TelephonyManager.DATA_ACTIVITY_INOUT
+import android.telephony.TelephonyManager.DATA_ACTIVITY_NONE
+import android.telephony.TelephonyManager.DATA_ACTIVITY_OUT
+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.dagger.SysUISingleton
+import com.android.systemui.demomode.DemoMode.COMMAND_NETWORK
+import com.android.systemui.demomode.DemoModeController
+import com.android.systemui.kairos.Events
+import com.android.systemui.kairos.ExperimentalKairosApi
+import com.android.systemui.kairosBuilder
+import com.android.systemui.statusbar.pipeline.mobile.data.repository.demo.model.FakeNetworkEventModel
+import com.android.systemui.statusbar.pipeline.mobile.data.repository.demo.model.FakeNetworkEventModel.Mobile
+import com.android.systemui.statusbar.pipeline.mobile.data.repository.demo.model.FakeNetworkEventModel.MobileDisabled
+import dagger.Binds
+import dagger.Provides
+import dagger.multibindings.ElementsIntoSet
+import javax.inject.Inject
+import javax.inject.Provider
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.map
+
+/**
+ * Data source that can map from demo mode commands to inputs into the
+ * [DemoMobileConnectionsRepositoryKairos]
+ */
+@ExperimentalKairosApi
+interface DemoModeMobileConnectionDataSourceKairos {
+ val mobileEvents: Events<FakeNetworkEventModel?>
+}
+
+@ExperimentalKairosApi
+@SysUISingleton
+class DemoModeMobileConnectionDataSourceKairosImpl
+@Inject
+constructor(demoModeController: DemoModeController) :
+ KairosBuilder by kairosBuilder(), DemoModeMobileConnectionDataSourceKairos {
+ private val demoCommandStream: Flow<Bundle> =
+ demoModeController.demoFlowForCommand(COMMAND_NETWORK)
+
+ // If the args contains "mobile", then all of the args are relevant. It's just the way demo mode
+ // commands work and it's a little silly
+ private val _mobileCommands: Flow<FakeNetworkEventModel?> =
+ demoCommandStream.map { args -> args.toMobileEvent() }
+ override val mobileEvents: Events<FakeNetworkEventModel?> = buildEvents {
+ _mobileCommands.toEvents()
+ }
+
+ private fun Bundle.toMobileEvent(): FakeNetworkEventModel? {
+ val mobile = getString("mobile") ?: return null
+ return if (mobile == "show") {
+ activeMobileEvent()
+ } else {
+ MobileDisabled(subId = getString("slot")?.toInt())
+ }
+ }
+
+ /** Parse a valid mobile command string into a network event */
+ private fun Bundle.activeMobileEvent(): Mobile {
+ // There are many key/value pairs supported by mobile demo mode. Bear with me here
+ val level = getString("level")?.toInt()
+ val dataType = getString("datatype")?.toDataType()
+ val slot = getString("slot")?.toInt()
+ val carrierId = getString("carrierid")?.toInt()
+ val inflateStrength = getString("inflate").toBoolean()
+ val activity = getString("activity")?.toActivity()
+ val carrierNetworkChange = getString("carriernetworkchange") == "show"
+ val roaming = getString("roam") == "show"
+ val name = getString("networkname") ?: "demo mode"
+ val slice = getString("slice").toBoolean()
+ val ntn = getString("ntn").toBoolean()
+
+ return Mobile(
+ level = level,
+ dataType = dataType,
+ subId = slot,
+ carrierId = carrierId,
+ inflateStrength = inflateStrength,
+ activity = activity,
+ carrierNetworkChange = carrierNetworkChange,
+ roaming = roaming,
+ name = name,
+ slice = slice,
+ ntn = ntn,
+ )
+ }
+
+ @dagger.Module
+ interface Module {
+ @Binds
+ fun bindImpl(
+ impl: DemoModeMobileConnectionDataSourceKairosImpl
+ ): DemoModeMobileConnectionDataSourceKairos
+
+ companion object {
+ @Provides
+ @ElementsIntoSet
+ fun kairosActivatable(
+ impl: Provider<DemoModeMobileConnectionDataSourceKairosImpl>
+ ): Set<@JvmSuppressWildcards KairosActivatable> =
+ if (Flags.statusBarMobileIconKairos()) setOf(impl.get()) else emptySet()
+ }
+ }
+}
+
+private fun String.toDataType(): MobileIconGroup =
+ when (this) {
+ "1x" -> TelephonyIcons.ONE_X
+ "3g" -> TelephonyIcons.THREE_G
+ "4g" -> TelephonyIcons.FOUR_G
+ "4g+" -> TelephonyIcons.FOUR_G_PLUS
+ "5g" -> TelephonyIcons.NR_5G
+ "5ge" -> TelephonyIcons.LTE_CA_5G_E
+ "5g+" -> TelephonyIcons.NR_5G_PLUS
+ "e" -> TelephonyIcons.E
+ "g" -> TelephonyIcons.G
+ "h" -> TelephonyIcons.H
+ "h+" -> TelephonyIcons.H_PLUS
+ "lte" -> TelephonyIcons.LTE
+ "lte+" -> TelephonyIcons.LTE_PLUS
+ "dis" -> TelephonyIcons.DATA_DISABLED
+ "not" -> TelephonyIcons.NOT_DEFAULT_DATA
+ else -> TelephonyIcons.UNKNOWN
+ }
+
+@DataActivityType
+private fun String.toActivity(): Int =
+ when (this) {
+ "inout" -> DATA_ACTIVITY_INOUT
+ "in" -> DATA_ACTIVITY_IN
+ "out" -> DATA_ACTIVITY_OUT
+ else -> DATA_ACTIVITY_NONE
+ }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/demo/model/FakeNetworkEventModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/demo/model/FakeNetworkEventModel.kt
index 42171d0dc2b5..54162bb75f3e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/demo/model/FakeNetworkEventModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/demo/model/FakeNetworkEventModel.kt
@@ -25,11 +25,13 @@ import com.android.settingslib.SignalIcon
* Nullable fields represent optional command line arguments
*/
sealed interface FakeNetworkEventModel {
+ // Null means the default (chosen by the repository)
+ val subId: Int?
+
data class Mobile(
val level: Int?,
val dataType: SignalIcon.MobileIconGroup?,
- // Null means the default (chosen by the repository)
- val subId: Int?,
+ override val subId: Int?,
val carrierId: Int?,
val inflateStrength: Boolean = false,
@DataActivityType val activity: Int?,
@@ -40,8 +42,5 @@ sealed interface FakeNetworkEventModel {
val ntn: Boolean = false,
) : FakeNetworkEventModel
- data class MobileDisabled(
- // Null means the default (chosen by the repository)
- val subId: Int?
- ) : FakeNetworkEventModel
+ data class MobileDisabled(override val subId: Int?) : FakeNetworkEventModel
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/CarrierMergedConnectionRepositoryKairos.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/CarrierMergedConnectionRepositoryKairos.kt
new file mode 100644
index 000000000000..d61d11bcf6b7
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/CarrierMergedConnectionRepositoryKairos.kt
@@ -0,0 +1,208 @@
+/*
+ * 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.data.repository.prod
+
+import android.telephony.CellSignalStrength.SIGNAL_STRENGTH_NONE_OR_UNKNOWN
+import android.telephony.SubscriptionManager.INVALID_SUBSCRIPTION_ID
+import android.telephony.TelephonyManager
+import android.util.Log
+import com.android.systemui.KairosBuilder
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.kairos.ExperimentalKairosApi
+import com.android.systemui.kairos.State
+import com.android.systemui.kairos.combine
+import com.android.systemui.kairos.map
+import com.android.systemui.kairos.stateOf
+import com.android.systemui.kairosBuilder
+import com.android.systemui.log.table.TableLogBuffer
+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
+import com.android.systemui.statusbar.pipeline.mobile.data.repository.MobileConnectionRepositoryKairos
+import com.android.systemui.statusbar.pipeline.mobile.data.repository.MobileConnectionRepositoryKairos.Companion.DEFAULT_NUM_LEVELS
+import com.android.systemui.statusbar.pipeline.shared.data.model.DataActivityModel
+import com.android.systemui.statusbar.pipeline.wifi.data.repository.WifiRepository
+import com.android.systemui.statusbar.pipeline.wifi.shared.model.WifiNetworkModel
+import javax.inject.Inject
+
+/**
+ * A repository implementation for a carrier merged (aka VCN) network. A carrier merged network is
+ * delivered to SysUI as a wifi network (see [WifiNetworkModel.CarrierMerged], but is visually
+ * displayed as a mobile network triangle.
+ *
+ * See [android.net.wifi.WifiInfo.isCarrierMerged] for more information.
+ *
+ * See [MobileConnectionRepositoryImpl] for a repository implementation of a typical mobile
+ * connection.
+ */
+@ExperimentalKairosApi
+class CarrierMergedConnectionRepositoryKairos(
+ override val subId: Int,
+ override val tableLogBuffer: TableLogBuffer,
+ private val telephonyManager: TelephonyManager,
+ val wifiRepository: WifiRepository,
+ override val isInEcmMode: State<Boolean>,
+) : MobileConnectionRepositoryKairos, KairosBuilder by kairosBuilder() {
+ init {
+ if (telephonyManager.subscriptionId != subId) {
+ error(
+ """CarrierMergedRepo: TelephonyManager should be created with subId($subId).
+ | Found ${telephonyManager.subscriptionId} instead."""
+ .trimMargin()
+ )
+ }
+ }
+
+ private val isWifiEnabled: State<Boolean> = buildState {
+ wifiRepository.isWifiEnabled.toState()
+ }
+ private val isWifiDefault: State<Boolean> = buildState {
+ wifiRepository.isWifiDefault.toState()
+ }
+ private val wifiNetwork: State<WifiNetworkModel> = buildState {
+ wifiRepository.wifiNetwork.toState()
+ }
+
+ /**
+ * Outputs the carrier merged network to use, or null if we don't have a valid carrier merged
+ * network.
+ */
+ private val network: State<WifiNetworkModel.CarrierMerged?> =
+ combine(isWifiEnabled, isWifiDefault, wifiNetwork) { isEnabled, isDefault, network ->
+ when {
+ !isEnabled -> null
+ !isDefault -> null
+ network !is WifiNetworkModel.CarrierMerged -> null
+ network.subscriptionId != subId -> {
+ Log.w(
+ TAG,
+ """Connection repo subId=$subId does not equal wifi repo
+ | subId=${network.subscriptionId}; not showing carrier merged"""
+ .trimMargin(),
+ )
+ null
+ }
+ else -> network
+ }
+ }
+
+ override val cdmaRoaming: State<Boolean> = stateOf(ROAMING)
+
+ override val networkName: State<NetworkNameModel> =
+ // The SIM operator name should be the same throughout the lifetime of a subId, **but**
+ // it may not be available when this repo is created because it takes time to load. To
+ // be safe, we re-fetch it each time the network has changed.
+ network.map { NetworkNameModel.SimDerived(telephonyManager.simOperatorName) }
+
+ override val carrierName: State<NetworkNameModel>
+ get() = networkName
+
+ override val numberOfLevels: State<Int> =
+ wifiNetwork.map {
+ if (it is WifiNetworkModel.CarrierMerged) {
+ it.numberOfLevels
+ } else {
+ DEFAULT_NUM_LEVELS
+ }
+ }
+
+ override val primaryLevel: State<Int> =
+ network.map { it?.level ?: SIGNAL_STRENGTH_NONE_OR_UNKNOWN }
+
+ override val cdmaLevel: State<Int> =
+ network.map { it?.level ?: SIGNAL_STRENGTH_NONE_OR_UNKNOWN }
+
+ override val dataActivityDirection: State<DataActivityModel> = buildState {
+ wifiRepository.wifiActivity.toState()
+ }
+
+ override val resolvedNetworkType: State<ResolvedNetworkType> =
+ network.map {
+ if (it != null) {
+ ResolvedNetworkType.CarrierMergedNetworkType
+ } else {
+ ResolvedNetworkType.UnknownNetworkType
+ }
+ }
+
+ override val dataConnectionState: State<DataConnectionState> =
+ network.map {
+ if (it != null) {
+ DataConnectionState.Connected
+ } else {
+ DataConnectionState.Disconnected
+ }
+ }
+
+ override val isRoaming: State<Boolean> = stateOf(false)
+ override val carrierId: State<Int> = stateOf(INVALID_SUBSCRIPTION_ID)
+ override val inflateSignalStrength: State<Boolean> = stateOf(false)
+ override val allowNetworkSliceIndicator: State<Boolean> = stateOf(false)
+ override val isEmergencyOnly: State<Boolean> = stateOf(false)
+ override val operatorAlphaShort: State<String?> = stateOf(null)
+ override val isInService: State<Boolean> = stateOf(true)
+ override val isNonTerrestrial: State<Boolean> = stateOf(false)
+ override val isGsm: State<Boolean> = stateOf(false)
+ override val carrierNetworkChangeActive: State<Boolean> = stateOf(false)
+ override val satelliteLevel: State<Int> = stateOf(0)
+
+ /**
+ * Carrier merged connections happen over wifi but are displayed as a mobile triangle. Because
+ * they occur over wifi, it's possible to have a valid carrier merged connection even during
+ * airplane mode. See b/291993542.
+ */
+ override val isAllowedDuringAirplaneMode: State<Boolean> = stateOf(true)
+
+ /**
+ * It's not currently considered possible that a carrier merged network can have these
+ * prioritized capabilities. If we need to track them, we can add the same check as is in
+ * [MobileConnectionRepositoryImpl].
+ */
+ override val hasPrioritizedNetworkCapabilities: State<Boolean> = stateOf(false)
+
+ override val dataEnabled: State<Boolean>
+ get() = isWifiEnabled
+
+ companion object {
+ // Carrier merged is never roaming
+ private const val ROAMING = false
+ }
+
+ @SysUISingleton
+ class Factory
+ @Inject
+ constructor(
+ private val telephonyManager: TelephonyManager,
+ private val wifiRepository: WifiRepository,
+ ) {
+ fun build(
+ subId: Int,
+ mobileLogger: TableLogBuffer,
+ mobileRepo: MobileConnectionRepositoryKairos,
+ ): CarrierMergedConnectionRepositoryKairos {
+ return CarrierMergedConnectionRepositoryKairos(
+ subId,
+ mobileLogger,
+ telephonyManager.createForSubscriptionId(subId),
+ wifiRepository,
+ mobileRepo.isInEcmMode,
+ )
+ }
+ }
+}
+
+private const val TAG = "CarrierMergedConnectionRepository"
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/FullMobileConnectionRepositoryKairos.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/FullMobileConnectionRepositoryKairos.kt
new file mode 100644
index 000000000000..1a8ca9577bd7
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/FullMobileConnectionRepositoryKairos.kt
@@ -0,0 +1,254 @@
+/*
+ * 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.data.repository.prod
+
+import android.util.IndentingPrintWriter
+import androidx.annotation.VisibleForTesting
+import com.android.systemui.KairosBuilder
+import com.android.systemui.kairos.BuildSpec
+import com.android.systemui.kairos.ExperimentalKairosApi
+import com.android.systemui.kairos.State
+import com.android.systemui.kairos.flatMap
+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
+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.MobileConnectionRepositoryKairos
+import com.android.systemui.statusbar.pipeline.shared.data.model.DataActivityModel
+import dagger.assisted.Assisted
+import dagger.assisted.AssistedFactory
+import dagger.assisted.AssistedInject
+import java.io.PrintWriter
+
+/**
+ * A repository that fully implements a mobile connection.
+ *
+ * This connection could either be a typical mobile connection (see [MobileConnectionRepositoryImpl]
+ * or a carrier merged connection (see [CarrierMergedConnectionRepository]). This repository
+ * switches between the two types of connections based on whether the connection is currently
+ * carrier merged.
+ */
+@ExperimentalKairosApi
+class FullMobileConnectionRepositoryKairos
+@AssistedInject
+constructor(
+ @Assisted override val subId: Int,
+ @Assisted override val tableLogBuffer: TableLogBuffer,
+ @Assisted private val mobileRepo: MobileConnectionRepositoryKairos,
+ @Assisted private val carrierMergedRepoSpec: BuildSpec<MobileConnectionRepositoryKairos>,
+ @Assisted private val isCarrierMerged: State<Boolean>,
+) : MobileConnectionRepositoryKairos, KairosBuilder by kairosBuilder() {
+
+ init {
+ onActivated {
+ logDiffsForTable(isCarrierMerged, tableLogBuffer, columnName = "isCarrierMerged")
+ }
+ }
+
+ @VisibleForTesting
+ val activeRepo: State<MobileConnectionRepositoryKairos> = buildState {
+ isCarrierMerged.mapLatestBuild { merged ->
+ if (merged) {
+ carrierMergedRepoSpec.applySpec()
+ } else {
+ mobileRepo
+ }
+ }
+ }
+
+ override val carrierId: State<Int> = activeRepo.flatMap { it.carrierId }
+
+ override val cdmaRoaming: State<Boolean> = activeRepo.flatMap { it.cdmaRoaming }
+
+ override val isEmergencyOnly: State<Boolean> =
+ activeRepo
+ .flatMap { it.isEmergencyOnly }
+ .also {
+ onActivated { logDiffsForTable(it, tableLogBuffer, columnName = COL_EMERGENCY) }
+ }
+
+ override val isRoaming: State<Boolean> =
+ activeRepo
+ .flatMap { it.isRoaming }
+ .also { onActivated { logDiffsForTable(it, tableLogBuffer, columnName = COL_ROAMING) } }
+
+ override val operatorAlphaShort: State<String?> =
+ activeRepo
+ .flatMap { it.operatorAlphaShort }
+ .also {
+ onActivated { logDiffsForTable(it, tableLogBuffer, columnName = COL_OPERATOR) }
+ }
+
+ override val isInService: State<Boolean> =
+ activeRepo
+ .flatMap { it.isInService }
+ .also {
+ onActivated { logDiffsForTable(it, tableLogBuffer, columnName = COL_IS_IN_SERVICE) }
+ }
+
+ override val isNonTerrestrial: State<Boolean> =
+ activeRepo
+ .flatMap { it.isNonTerrestrial }
+ .also { onActivated { logDiffsForTable(it, tableLogBuffer, columnName = COL_IS_NTN) } }
+
+ override val isGsm: State<Boolean> =
+ activeRepo
+ .flatMap { it.isGsm }
+ .also { onActivated { logDiffsForTable(it, tableLogBuffer, columnName = COL_IS_GSM) } }
+
+ override val cdmaLevel: State<Int> =
+ activeRepo
+ .flatMap { it.cdmaLevel }
+ .also {
+ onActivated { logDiffsForTable(it, tableLogBuffer, columnName = COL_CDMA_LEVEL) }
+ }
+
+ override val primaryLevel: State<Int> =
+ activeRepo
+ .flatMap { it.primaryLevel }
+ .also {
+ onActivated { logDiffsForTable(it, tableLogBuffer, columnName = COL_PRIMARY_LEVEL) }
+ }
+
+ override val satelliteLevel: State<Int> =
+ activeRepo
+ .flatMap { it.satelliteLevel }
+ .also {
+ onActivated {
+ logDiffsForTable(it, tableLogBuffer, columnName = COL_SATELLITE_LEVEL)
+ }
+ }
+
+ override val dataConnectionState: State<DataConnectionState> =
+ activeRepo
+ .flatMap { it.dataConnectionState }
+ .also { onActivated { logDiffsForTable(it, tableLogBuffer, columnPrefix = "") } }
+
+ override val dataActivityDirection: State<DataActivityModel> =
+ activeRepo
+ .flatMap { it.dataActivityDirection }
+ .also { onActivated { logDiffsForTable(it, tableLogBuffer, columnPrefix = "") } }
+
+ override val carrierNetworkChangeActive: State<Boolean> =
+ activeRepo
+ .flatMap { it.carrierNetworkChangeActive }
+ .also {
+ onActivated {
+ logDiffsForTable(it, tableLogBuffer, columnName = COL_CARRIER_NETWORK_CHANGE)
+ }
+ }
+
+ override val resolvedNetworkType: State<ResolvedNetworkType> =
+ activeRepo
+ .flatMap { it.resolvedNetworkType }
+ .also { onActivated { logDiffsForTable(it, tableLogBuffer, columnPrefix = "") } }
+
+ override val dataEnabled: State<Boolean> =
+ activeRepo
+ .flatMap { it.dataEnabled }
+ .also {
+ onActivated { logDiffsForTable(it, tableLogBuffer, columnName = "dataEnabled") }
+ }
+
+ override val inflateSignalStrength: State<Boolean> =
+ activeRepo
+ .flatMap { it.inflateSignalStrength }
+ .also { onActivated { logDiffsForTable(it, tableLogBuffer, columnName = "inflate") } }
+
+ override val allowNetworkSliceIndicator: State<Boolean> =
+ activeRepo
+ .flatMap { it.allowNetworkSliceIndicator }
+ .also {
+ onActivated { logDiffsForTable(it, tableLogBuffer, columnName = "allowSlice") }
+ }
+
+ override val numberOfLevels: State<Int> = activeRepo.flatMap { it.numberOfLevels }
+
+ override val networkName: State<NetworkNameModel> =
+ activeRepo
+ .flatMap { it.networkName }
+ .also { onActivated { logDiffsForTable(it, tableLogBuffer, columnPrefix = "intent") } }
+
+ override val carrierName: State<NetworkNameModel> =
+ activeRepo
+ .flatMap { it.carrierName }
+ .also { onActivated { logDiffsForTable(it, tableLogBuffer, columnPrefix = "sub") } }
+
+ override val isAllowedDuringAirplaneMode: State<Boolean> =
+ activeRepo.flatMap { it.isAllowedDuringAirplaneMode }
+
+ override val hasPrioritizedNetworkCapabilities: State<Boolean> =
+ activeRepo.flatMap { it.hasPrioritizedNetworkCapabilities }
+
+ override val isInEcmMode: State<Boolean> = activeRepo.flatMap { it.isInEcmMode }
+
+ private var dumpCache: DumpCache? = null
+
+ private data class DumpCache(
+ val isCarrierMerged: Boolean,
+ val activeRepo: MobileConnectionRepositoryKairos,
+ )
+
+ fun dump(pw: PrintWriter) {
+ val cache = dumpCache ?: return
+ val ipw = IndentingPrintWriter(pw, " ")
+
+ ipw.println("MobileConnectionRepository[$subId]")
+ ipw.increaseIndent()
+
+ ipw.println("carrierMerged=${cache.isCarrierMerged}")
+
+ ipw.print("Type (cellular or carrier merged): ")
+ when (cache.activeRepo) {
+ is CarrierMergedConnectionRepositoryKairos -> ipw.println("Carrier merged")
+ is MobileConnectionRepositoryKairosImpl -> ipw.println("Cellular")
+ }
+
+ ipw.increaseIndent()
+ ipw.println("Provider: ${cache.activeRepo}")
+ ipw.decreaseIndent()
+
+ ipw.decreaseIndent()
+ }
+
+ @AssistedFactory
+ interface Factory {
+ fun create(
+ subId: Int,
+ mobileLogger: TableLogBuffer,
+ isCarrierMerged: State<Boolean>,
+ mobileRepo: MobileConnectionRepositoryKairos,
+ mergedRepoSpec: BuildSpec<MobileConnectionRepositoryKairos>,
+ ): FullMobileConnectionRepositoryKairos
+ }
+
+ companion object {
+ const val COL_CARRIER_ID = "carrierId"
+ const val COL_CARRIER_NETWORK_CHANGE = "carrierNetworkChangeActive"
+ const val COL_CDMA_LEVEL = "cdmaLevel"
+ const val COL_EMERGENCY = "emergencyOnly"
+ const val COL_IS_NTN = "isNtn"
+ const val COL_IS_GSM = "isGsm"
+ const val COL_IS_IN_SERVICE = "isInService"
+ const val COL_OPERATOR = "operatorName"
+ const val COL_PRIMARY_LEVEL = "primaryLevel"
+ const val COL_SATELLITE_LEVEL = "satelliteLevel"
+ const val COL_ROAMING = "roaming"
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionRepositoryImpl.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionRepositoryImpl.kt
index b4a45e24a0cb..bf7c2990b5ab 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionRepositoryImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionRepositoryImpl.kt
@@ -542,6 +542,10 @@ sealed interface CallbackEvent {
data class OnCarrierRoamingNtnSignalStrengthChanged(val signalStrength: NtnSignalStrength) :
CallbackEvent
+
+ data class OnCallBackModeStarted(val type: Int) : CallbackEvent
+
+ data class OnCallBackModeStopped(val type: Int) : CallbackEvent
}
/**
@@ -560,6 +564,8 @@ data class TelephonyCallbackState(
val onCarrierRoamingNtnSignalStrengthChanged:
CallbackEvent.OnCarrierRoamingNtnSignalStrengthChanged? =
null,
+ val addedCallbackModes: Set<Int> = emptySet(),
+ val removedCallbackModes: Set<Int> = emptySet(),
) {
fun applyEvent(event: CallbackEvent): TelephonyCallbackState {
return when (event) {
@@ -578,6 +584,37 @@ data class TelephonyCallbackState(
is CallbackEvent.OnSignalStrengthChanged -> copy(onSignalStrengthChanged = event)
is CallbackEvent.OnCarrierRoamingNtnSignalStrengthChanged ->
copy(onCarrierRoamingNtnSignalStrengthChanged = event)
+ is CallbackEvent.OnCallBackModeStarted -> {
+ copy(
+ addedCallbackModes =
+ if (event.type !in removedCallbackModes) {
+ addedCallbackModes + event.type
+ } else {
+ addedCallbackModes
+ },
+ removedCallbackModes =
+ if (event.type !in addedCallbackModes) {
+ removedCallbackModes - event.type
+ } else {
+ removedCallbackModes
+ },
+ )
+ }
+ is CallbackEvent.OnCallBackModeStopped ->
+ copy(
+ addedCallbackModes =
+ if (event.type !in removedCallbackModes) {
+ addedCallbackModes - event.type
+ } else {
+ addedCallbackModes
+ },
+ removedCallbackModes =
+ if (event.type !in addedCallbackModes) {
+ removedCallbackModes + event.type
+ } else {
+ removedCallbackModes
+ },
+ )
}
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionRepositoryKairosAdapter.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionRepositoryKairosAdapter.kt
new file mode 100644
index 000000000000..9b37f4896878
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionRepositoryKairosAdapter.kt
@@ -0,0 +1,97 @@
+/*
+ * 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.data.repository.prod
+
+import com.android.systemui.kairos.BuildScope
+import com.android.systemui.kairos.ExperimentalKairosApi
+import com.android.systemui.log.table.TableLogBuffer
+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
+import com.android.systemui.statusbar.pipeline.mobile.data.model.SystemUiCarrierConfig
+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.shared.data.model.DataActivityModel
+import com.android.systemui.util.kotlin.Producer
+import kotlinx.coroutines.flow.StateFlow
+
+@ExperimentalKairosApi
+fun BuildScope.MobileConnectionRepositoryKairosAdapter(
+ kairosRepo: MobileConnectionRepositoryKairos,
+ carrierConfig: SystemUiCarrierConfig,
+) =
+ MobileConnectionRepositoryKairosAdapter(
+ subId = kairosRepo.subId,
+ carrierId = kairosRepo.carrierId.toStateFlow(),
+ inflateSignalStrength = carrierConfig.shouldInflateSignalStrength,
+ allowNetworkSliceIndicator = carrierConfig.allowNetworkSliceIndicator,
+ tableLogBuffer = kairosRepo.tableLogBuffer,
+ isEmergencyOnly = kairosRepo.isEmergencyOnly.toStateFlow(),
+ isRoaming = kairosRepo.isRoaming.toStateFlow(),
+ operatorAlphaShort = kairosRepo.operatorAlphaShort.toStateFlow(),
+ isInService = kairosRepo.isInService.toStateFlow(),
+ isNonTerrestrial = kairosRepo.isNonTerrestrial.toStateFlow(),
+ isGsm = kairosRepo.isGsm.toStateFlow(),
+ cdmaLevel = kairosRepo.cdmaLevel.toStateFlow(),
+ primaryLevel = kairosRepo.primaryLevel.toStateFlow(),
+ satelliteLevel = kairosRepo.satelliteLevel.toStateFlow(),
+ dataConnectionState = kairosRepo.dataConnectionState.toStateFlow(),
+ dataActivityDirection = kairosRepo.dataActivityDirection.toStateFlow(),
+ carrierNetworkChangeActive = kairosRepo.carrierNetworkChangeActive.toStateFlow(),
+ resolvedNetworkType = kairosRepo.resolvedNetworkType.toStateFlow(),
+ numberOfLevels = kairosRepo.numberOfLevels.toStateFlow(),
+ dataEnabled = kairosRepo.dataEnabled.toStateFlow(),
+ cdmaRoaming = kairosRepo.cdmaRoaming.toStateFlow(),
+ networkName = kairosRepo.networkName.toStateFlow(),
+ carrierName = kairosRepo.carrierName.toStateFlow(),
+ isAllowedDuringAirplaneMode = kairosRepo.isAllowedDuringAirplaneMode.toStateFlow(),
+ hasPrioritizedNetworkCapabilities =
+ kairosRepo.hasPrioritizedNetworkCapabilities.toStateFlow(),
+ isInEcmMode = { kairosNetwork.transact { kairosRepo.isInEcmMode.sample() } },
+ )
+
+@ExperimentalKairosApi
+class MobileConnectionRepositoryKairosAdapter(
+ override val subId: Int,
+ override val carrierId: StateFlow<Int>,
+ override val inflateSignalStrength: StateFlow<Boolean>,
+ override val allowNetworkSliceIndicator: StateFlow<Boolean>,
+ override val tableLogBuffer: TableLogBuffer,
+ override val isEmergencyOnly: StateFlow<Boolean>,
+ override val isRoaming: StateFlow<Boolean>,
+ override val operatorAlphaShort: StateFlow<String?>,
+ override val isInService: StateFlow<Boolean>,
+ override val isNonTerrestrial: StateFlow<Boolean>,
+ override val isGsm: StateFlow<Boolean>,
+ override val cdmaLevel: StateFlow<Int>,
+ override val primaryLevel: StateFlow<Int>,
+ override val satelliteLevel: StateFlow<Int>,
+ override val dataConnectionState: StateFlow<DataConnectionState>,
+ override val dataActivityDirection: StateFlow<DataActivityModel>,
+ override val carrierNetworkChangeActive: StateFlow<Boolean>,
+ override val resolvedNetworkType: StateFlow<ResolvedNetworkType>,
+ override val numberOfLevels: StateFlow<Int>,
+ override val dataEnabled: StateFlow<Boolean>,
+ override val cdmaRoaming: StateFlow<Boolean>,
+ override val networkName: StateFlow<NetworkNameModel>,
+ override val carrierName: StateFlow<NetworkNameModel>,
+ override val isAllowedDuringAirplaneMode: StateFlow<Boolean>,
+ override val hasPrioritizedNetworkCapabilities: StateFlow<Boolean>,
+ private val isInEcmMode: Producer<Boolean>,
+) : MobileConnectionRepository {
+ override suspend fun isInEcmMode(): Boolean = isInEcmMode.get()
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionRepositoryKairosImpl.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionRepositoryKairosImpl.kt
new file mode 100644
index 000000000000..abe72e17163b
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionRepositoryKairosImpl.kt
@@ -0,0 +1,485 @@
+/*
+ * 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.data.repository.prod
+
+import android.annotation.SuppressLint
+import android.content.BroadcastReceiver
+import android.content.Context
+import android.content.Intent
+import android.content.IntentFilter
+import android.net.ConnectivityManager
+import android.net.ConnectivityManager.NetworkCallback
+import android.net.Network
+import android.net.NetworkCapabilities
+import android.net.NetworkRequest
+import android.telephony.CellSignalStrength.SIGNAL_STRENGTH_NONE_OR_UNKNOWN
+import android.telephony.CellSignalStrengthCdma
+import android.telephony.ServiceState
+import android.telephony.SignalStrength
+import android.telephony.SubscriptionManager.EXTRA_SUBSCRIPTION_INDEX
+import android.telephony.SubscriptionManager.INVALID_SUBSCRIPTION_ID
+import android.telephony.TelephonyCallback
+import android.telephony.TelephonyDisplayInfo
+import android.telephony.TelephonyDisplayInfo.OVERRIDE_NETWORK_TYPE_NONE
+import android.telephony.TelephonyManager
+import android.telephony.TelephonyManager.ERI_FLASH
+import android.telephony.TelephonyManager.ERI_ON
+import android.telephony.TelephonyManager.EXTRA_SUBSCRIPTION_ID
+import android.telephony.TelephonyManager.NETWORK_TYPE_UNKNOWN
+import android.telephony.TelephonyManager.UNKNOWN_CARRIER_ID
+import com.android.settingslib.Utils
+import com.android.systemui.KairosBuilder
+import com.android.systemui.broadcast.BroadcastDispatcher
+import com.android.systemui.dagger.qualifiers.Background
+import com.android.systemui.flags.FeatureFlagsClassic
+import com.android.systemui.flags.Flags.ROAMING_INDICATOR_VIA_DISPLAY_INFO
+import com.android.systemui.kairos.Events
+import com.android.systemui.kairos.ExperimentalKairosApi
+import com.android.systemui.kairos.State
+import com.android.systemui.kairos.Transactional
+import com.android.systemui.kairos.awaitClose
+import com.android.systemui.kairos.coalescingEvents
+import com.android.systemui.kairos.conflatedEvents
+import com.android.systemui.kairos.map
+import com.android.systemui.kairos.mapNotNull
+import com.android.systemui.kairos.stateOf
+import com.android.systemui.kairos.transactionally
+import com.android.systemui.kairosBuilder
+import com.android.systemui.log.table.TableLogBuffer
+import com.android.systemui.statusbar.pipeline.mobile.data.MobileInputLogger
+import com.android.systemui.statusbar.pipeline.mobile.data.model.DataConnectionState
+import com.android.systemui.statusbar.pipeline.mobile.data.model.DataConnectionState.Disconnected
+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.model.ResolvedNetworkType.DefaultNetworkType
+import com.android.systemui.statusbar.pipeline.mobile.data.model.ResolvedNetworkType.OverrideNetworkType
+import com.android.systemui.statusbar.pipeline.mobile.data.model.ResolvedNetworkType.UnknownNetworkType
+import com.android.systemui.statusbar.pipeline.mobile.data.model.SubscriptionModel
+import com.android.systemui.statusbar.pipeline.mobile.data.model.SystemUiCarrierConfig
+import com.android.systemui.statusbar.pipeline.mobile.data.model.toDataConnectionType
+import com.android.systemui.statusbar.pipeline.mobile.data.model.toNetworkNameModel
+import com.android.systemui.statusbar.pipeline.mobile.data.repository.MobileConnectionRepository.Companion.DEFAULT_NUM_LEVELS
+import com.android.systemui.statusbar.pipeline.mobile.data.repository.MobileConnectionRepositoryKairos
+import com.android.systemui.statusbar.pipeline.mobile.util.MobileMappingsProxy
+import com.android.systemui.statusbar.pipeline.shared.data.model.DataActivityModel
+import com.android.systemui.statusbar.pipeline.shared.data.model.toMobileDataActivityModel
+import dagger.assisted.Assisted
+import dagger.assisted.AssistedFactory
+import dagger.assisted.AssistedInject
+import java.time.Duration
+import kotlinx.coroutines.CoroutineDispatcher
+import kotlinx.coroutines.asExecutor
+import kotlinx.coroutines.flow.SharingStarted
+import kotlinx.coroutines.flow.filter
+import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.flow.scan
+import kotlinx.coroutines.withContext
+
+/**
+ * A repository implementation for a typical mobile connection (as opposed to a carrier merged
+ * connection -- see [CarrierMergedConnectionRepository]).
+ */
+@ExperimentalKairosApi
+class MobileConnectionRepositoryKairosImpl
+@AssistedInject
+constructor(
+ @Assisted override val subId: Int,
+ private val context: Context,
+ @Assisted subscriptionModel: State<SubscriptionModel?>,
+ @Assisted defaultNetworkName: NetworkNameModel,
+ @Assisted networkNameSeparator: String,
+ connectivityManager: ConnectivityManager,
+ @Assisted private val telephonyManager: TelephonyManager,
+ @Assisted systemUiCarrierConfig: SystemUiCarrierConfig,
+ broadcastDispatcher: BroadcastDispatcher,
+ private val mobileMappingsProxy: MobileMappingsProxy,
+ @Background private val bgDispatcher: CoroutineDispatcher,
+ logger: MobileInputLogger,
+ @Assisted override val tableLogBuffer: TableLogBuffer,
+ flags: FeatureFlagsClassic,
+) : MobileConnectionRepositoryKairos, KairosBuilder by kairosBuilder() {
+
+ init {
+ if (telephonyManager.subscriptionId != subId) {
+ throw IllegalStateException(
+ "MobileRepo: TelephonyManager should be created with subId($subId). " +
+ "Found ${telephonyManager.subscriptionId} instead."
+ )
+ }
+ }
+
+ /**
+ * This flow defines the single shared connection to system_server via TelephonyCallback. Any
+ * new callback should be added to this listener and funneled through callbackEvents via a data
+ * class. See [CallbackEvent] for defining new callbacks.
+ *
+ * The reason we need to do this is because TelephonyManager limits the number of registered
+ * listeners per-process, so we don't want to create a new listener for every callback.
+ *
+ * A note on the design for back pressure here: We don't control _which_ telephony callback
+ * comes in first, since we register every relevant bit of information as a batch. E.g., if a
+ * downstream starts collecting on a field which is backed by
+ * [TelephonyCallback.ServiceStateListener], it's not possible for us to guarantee that _that_
+ * callback comes in -- the first callback could very well be
+ * [TelephonyCallback.DataActivityListener], which would promptly be dropped if we didn't keep
+ * it tracked. We use the [scan] operator here to track the most recent callback of _each type_
+ * here. See [TelephonyCallbackState] to see how the callbacks are stored.
+ */
+ private val callbackEvents: Events<TelephonyCallbackState> = buildEvents {
+ coalescingEvents(
+ initialValue = TelephonyCallbackState(),
+ coalesce = TelephonyCallbackState::applyEvent,
+ ) {
+ val callback =
+ object :
+ TelephonyCallback(),
+ TelephonyCallback.CarrierNetworkListener,
+ TelephonyCallback.CarrierRoamingNtnListener,
+ TelephonyCallback.DataActivityListener,
+ TelephonyCallback.DataConnectionStateListener,
+ TelephonyCallback.DataEnabledListener,
+ TelephonyCallback.DisplayInfoListener,
+ TelephonyCallback.ServiceStateListener,
+ TelephonyCallback.SignalStrengthsListener,
+ TelephonyCallback.EmergencyCallbackModeListener {
+
+ override fun onCarrierNetworkChange(active: Boolean) {
+ logger.logOnCarrierNetworkChange(active, subId)
+ emit(CallbackEvent.OnCarrierNetworkChange(active))
+ }
+
+ override fun onCarrierRoamingNtnModeChanged(active: Boolean) {
+ logger.logOnCarrierRoamingNtnModeChanged(active)
+ emit(CallbackEvent.OnCarrierRoamingNtnModeChanged(active))
+ }
+
+ override fun onDataActivity(direction: Int) {
+ logger.logOnDataActivity(direction, subId)
+ emit(CallbackEvent.OnDataActivity(direction))
+ }
+
+ override fun onDataEnabledChanged(enabled: Boolean, reason: Int) {
+ logger.logOnDataEnabledChanged(enabled, subId)
+ emit(CallbackEvent.OnDataEnabledChanged(enabled))
+ }
+
+ override fun onDataConnectionStateChanged(dataState: Int, networkType: Int) {
+ logger.logOnDataConnectionStateChanged(dataState, networkType, subId)
+ emit(CallbackEvent.OnDataConnectionStateChanged(dataState))
+ }
+
+ override fun onDisplayInfoChanged(telephonyDisplayInfo: TelephonyDisplayInfo) {
+ logger.logOnDisplayInfoChanged(telephonyDisplayInfo, subId)
+ emit(CallbackEvent.OnDisplayInfoChanged(telephonyDisplayInfo))
+ }
+
+ override fun onServiceStateChanged(serviceState: ServiceState) {
+ logger.logOnServiceStateChanged(serviceState, subId)
+ emit(CallbackEvent.OnServiceStateChanged(serviceState))
+ }
+
+ override fun onSignalStrengthsChanged(signalStrength: SignalStrength) {
+ logger.logOnSignalStrengthsChanged(signalStrength, subId)
+ emit(CallbackEvent.OnSignalStrengthChanged(signalStrength))
+ }
+
+ override fun onCallbackModeStarted(
+ type: Int,
+ timerDuration: Duration,
+ subId: Int,
+ ) {
+ // logger.logOnCallBackModeStarted(type, subId)
+ emit(CallbackEvent.OnCallBackModeStarted(type))
+ }
+
+ override fun onCallbackModeRestarted(
+ type: Int,
+ timerDuration: Duration,
+ subId: Int,
+ ) {
+ // no-op
+ }
+
+ override fun onCallbackModeStopped(type: Int, reason: Int, subId: Int) {
+ // logger.logOnCallBackModeStopped(type, reason, subId)
+ emit(CallbackEvent.OnCallBackModeStopped(type))
+ }
+ }
+ withContext(bgDispatcher) {
+ telephonyManager.registerTelephonyCallback(bgDispatcher.asExecutor(), callback)
+ }
+ awaitClose { telephonyManager.unregisterTelephonyCallback(callback) }
+ }
+ }
+
+ private val serviceState: State<ServiceState?> = buildState {
+ callbackEvents.mapNotNull { it.onServiceStateChanged?.serviceState }.holdState(null)
+ }
+
+ override val isEmergencyOnly: State<Boolean> = serviceState.map { it?.isEmergencyOnly == true }
+
+ private val displayInfo: State<TelephonyDisplayInfo?> = buildState {
+ callbackEvents.mapNotNull { it.onDisplayInfoChanged?.telephonyDisplayInfo }.holdState(null)
+ }
+
+ override val isRoaming: State<Boolean> =
+ if (flags.isEnabled(ROAMING_INDICATOR_VIA_DISPLAY_INFO)) {
+ displayInfo.map { it?.isRoaming == true }
+ } else {
+ serviceState.map { it?.roaming == true }
+ }
+
+ override val operatorAlphaShort: State<String?> = serviceState.map { it?.operatorAlphaShort }
+
+ override val isInService: State<Boolean> =
+ serviceState.map { it?.let(Utils::isInService) == true }
+
+ private val carrierRoamingNtnActive: State<Boolean> = buildState {
+ callbackEvents.mapNotNull { it.onCarrierRoamingNtnModeChanged?.active }.holdState(false)
+ }
+
+ override val isNonTerrestrial: State<Boolean>
+ get() = carrierRoamingNtnActive
+
+ private val signalStrength: State<SignalStrength?> = buildState {
+ callbackEvents.mapNotNull { it.onSignalStrengthChanged?.signalStrength }.holdState(null)
+ }
+
+ override val isGsm: State<Boolean> = signalStrength.map { it?.isGsm == true }
+
+ override val cdmaLevel: State<Int> =
+ signalStrength.map {
+ it?.getCellSignalStrengths(CellSignalStrengthCdma::class.java)?.firstOrNull()?.level
+ ?: SIGNAL_STRENGTH_NONE_OR_UNKNOWN
+ }
+
+ override val primaryLevel: State<Int> =
+ signalStrength.map { it?.level ?: SIGNAL_STRENGTH_NONE_OR_UNKNOWN }
+
+ override val satelliteLevel: State<Int> = buildState {
+ callbackEvents
+ .mapNotNull { it.onCarrierRoamingNtnSignalStrengthChanged?.signalStrength?.level }
+ .holdState(0)
+ }
+
+ override val dataConnectionState: State<DataConnectionState> = buildState {
+ callbackEvents
+ .mapNotNull { it.onDataConnectionStateChanged?.dataState?.toDataConnectionType() }
+ .holdState(Disconnected)
+ }
+
+ override val dataActivityDirection: State<DataActivityModel> = buildState {
+ callbackEvents
+ .mapNotNull { it.onDataActivity?.direction?.toMobileDataActivityModel() }
+ .holdState(DataActivityModel(hasActivityIn = false, hasActivityOut = false))
+ }
+
+ override val carrierNetworkChangeActive: State<Boolean> = buildState {
+ callbackEvents.mapNotNull { it.onCarrierNetworkChange?.active }.holdState(false)
+ }
+
+ private val telephonyDisplayInfo: State<TelephonyDisplayInfo?> = buildState {
+ callbackEvents.mapNotNull { it.onDisplayInfoChanged?.telephonyDisplayInfo }.holdState(null)
+ }
+
+ override val resolvedNetworkType: State<ResolvedNetworkType> =
+ telephonyDisplayInfo.map { displayInfo ->
+ displayInfo
+ ?.overrideNetworkType
+ ?.takeIf { it != OVERRIDE_NETWORK_TYPE_NONE }
+ ?.let { OverrideNetworkType(mobileMappingsProxy.toIconKeyOverride(it)) }
+ ?: displayInfo
+ ?.networkType
+ ?.takeIf { it != NETWORK_TYPE_UNKNOWN }
+ ?.let { DefaultNetworkType(mobileMappingsProxy.toIconKey(it)) }
+ ?: UnknownNetworkType
+ }
+
+ override val inflateSignalStrength: State<Boolean> = buildState {
+ systemUiCarrierConfig.shouldInflateSignalStrength.toState()
+ }
+
+ override val allowNetworkSliceIndicator: State<Boolean> = buildState {
+ systemUiCarrierConfig.allowNetworkSliceIndicator.toState()
+ }
+
+ override val numberOfLevels: State<Int> =
+ inflateSignalStrength.map { shouldInflate ->
+ if (shouldInflate) {
+ DEFAULT_NUM_LEVELS + 1
+ } else {
+ DEFAULT_NUM_LEVELS
+ }
+ }
+
+ override val carrierName: State<NetworkNameModel> =
+ subscriptionModel.map {
+ it?.let { model -> NetworkNameModel.SubscriptionDerived(model.carrierName) }
+ ?: defaultNetworkName
+ }
+
+ /**
+ * There are a few cases where we will need to poll [TelephonyManager] so we can update some
+ * internal state where callbacks aren't provided. Any of those events should be merged into
+ * this flow, which can be used to trigger the polling.
+ */
+ private val telephonyPollingEvent: Events<Unit> = callbackEvents.map {}
+
+ private val cdmaEnhancedRoamingIndicatorDisplayNumber: Transactional<Int?> = transactionally {
+ try {
+ telephonyManager.cdmaEnhancedRoamingIndicatorDisplayNumber
+ } catch (e: UnsupportedOperationException) {
+ // Handles the same as a function call failure
+ null
+ }
+ }
+
+ override val cdmaRoaming: State<Boolean> = buildState {
+ telephonyPollingEvent
+ .map {
+ val cdmaEri = cdmaEnhancedRoamingIndicatorDisplayNumber.sample()
+ cdmaEri == ERI_ON || cdmaEri == ERI_FLASH
+ }
+ .holdState(false)
+ }
+
+ override val carrierId: State<Int> = buildState {
+ broadcastDispatcher
+ .broadcastFlow(
+ filter =
+ IntentFilter(TelephonyManager.ACTION_SUBSCRIPTION_CARRIER_IDENTITY_CHANGED),
+ map = { intent, _ -> intent },
+ )
+ .filter { intent ->
+ intent.getIntExtra(EXTRA_SUBSCRIPTION_ID, INVALID_SUBSCRIPTION_ID) == subId
+ }
+ .map { it.carrierId() }
+ .toState(telephonyManager.simCarrierId)
+ }
+
+ /**
+ * BroadcastDispatcher does not handle sticky broadcasts, so we can't use it here. Note that we
+ * now use the [SharingStarted.Eagerly] strategy, because there have been cases where the sticky
+ * broadcast does not represent the correct state.
+ *
+ * See b/322432056 for context.
+ */
+ @SuppressLint("RegisterReceiverViaContext")
+ override val networkName: State<NetworkNameModel> = buildState {
+ conflatedEvents {
+ val receiver =
+ object : BroadcastReceiver() {
+ override fun onReceive(context: Context, intent: Intent) {
+ if (
+ intent.getIntExtra(
+ EXTRA_SUBSCRIPTION_INDEX,
+ INVALID_SUBSCRIPTION_ID,
+ ) == subId
+ ) {
+ logger.logServiceProvidersUpdatedBroadcast(intent)
+ emit(
+ intent.toNetworkNameModel(networkNameSeparator)
+ ?: defaultNetworkName
+ )
+ }
+ }
+ }
+
+ context.registerReceiver(
+ receiver,
+ IntentFilter(TelephonyManager.ACTION_SERVICE_PROVIDERS_UPDATED),
+ )
+
+ awaitClose { context.unregisterReceiver(receiver) }
+ }
+ .holdState(defaultNetworkName)
+ }
+
+ override val dataEnabled: State<Boolean> = buildState {
+ callbackEvents
+ .mapNotNull { it.onDataEnabledChanged?.enabled }
+ .holdState(telephonyManager.isDataConnectionAllowed)
+ }
+
+ override val isInEcmMode: State<Boolean> = buildState {
+ callbackEvents
+ .mapNotNull {
+ (it.addedCallbackModes to it.removedCallbackModes).takeIf { (added, removed) ->
+ added.isNotEmpty() || removed.isNotEmpty()
+ }
+ }
+ .foldState(emptySet<Int>()) { (added, removed), acc -> acc - removed + added }
+ .mapTransactionally { it.isNotEmpty() }
+ }
+
+ /** Typical mobile connections aren't available during airplane mode. */
+ override val isAllowedDuringAirplaneMode: State<Boolean> = stateOf(false)
+
+ /**
+ * Currently, a network with NET_CAPABILITY_PRIORITIZE_LATENCY is the only type of network that
+ * we consider to be a "network slice". _PRIORITIZE_BANDWIDTH may be added in the future. Any of
+ * these capabilities that are used here must also be represented in the
+ * self_certified_network_capabilities.xml config file
+ */
+ @SuppressLint("WrongConstant")
+ private val networkSliceRequest: NetworkRequest =
+ NetworkRequest.Builder()
+ .addCapability(NetworkCapabilities.NET_CAPABILITY_PRIORITIZE_LATENCY)
+ .setSubscriptionIds(setOf(subId))
+ .build()
+
+ @SuppressLint("MissingPermission")
+ override val hasPrioritizedNetworkCapabilities: State<Boolean> = buildState {
+ conflatedEvents {
+ // Our network callback listens only for this.subId && net_cap_prioritize_latency
+ // therefore our state is a simple mapping of whether or not that network exists
+ val callback =
+ object : NetworkCallback() {
+ override fun onAvailable(network: Network) {
+ logger.logPrioritizedNetworkAvailable(network.netId)
+ emit(true)
+ }
+
+ override fun onLost(network: Network) {
+ logger.logPrioritizedNetworkLost(network.netId)
+ emit(false)
+ }
+ }
+
+ connectivityManager.registerNetworkCallback(networkSliceRequest, callback)
+
+ awaitClose { connectivityManager.unregisterNetworkCallback(callback) }
+ }
+ .holdState(false)
+ }
+
+ @AssistedFactory
+ fun interface Factory {
+ fun create(
+ subId: Int,
+ mobileLogger: TableLogBuffer,
+ subscriptionModel: State<SubscriptionModel?>,
+ defaultNetworkName: NetworkNameModel,
+ networkNameSeparator: String,
+ systemUiCarrierConfig: SystemUiCarrierConfig,
+ telephonyManager: TelephonyManager,
+ ): MobileConnectionRepositoryKairosImpl
+ }
+}
+
+private fun Intent.carrierId(): Int =
+ getIntExtra(TelephonyManager.EXTRA_CARRIER_ID, UNKNOWN_CARRIER_ID)
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/data/repository/prod/MobileConnectionsRepositoryKairosImpl.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionsRepositoryKairosImpl.kt
new file mode 100644
index 000000000000..e46815954e64
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionsRepositoryKairosImpl.kt
@@ -0,0 +1,584 @@
+/*
+ * 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.data.repository.prod
+
+import android.annotation.SuppressLint
+import android.content.Context
+import android.content.Intent
+import android.content.IntentFilter
+import android.content.pm.PackageManager
+import android.telephony.CarrierConfigManager
+import android.telephony.SubscriptionInfo
+import android.telephony.SubscriptionManager
+import android.telephony.SubscriptionManager.INVALID_SUBSCRIPTION_ID
+import android.telephony.TelephonyCallback
+import android.telephony.TelephonyCallback.ActiveDataSubscriptionIdListener
+import android.telephony.TelephonyCallback.EmergencyCallbackModeListener
+import android.telephony.TelephonyManager
+import android.util.IndentingPrintWriter
+import com.android.internal.telephony.PhoneConstants
+import com.android.keyguard.KeyguardUpdateMonitor
+import com.android.keyguard.KeyguardUpdateMonitorCallback
+import com.android.settingslib.SignalIcon.MobileIconGroup
+import com.android.settingslib.mobile.MobileMappings.Config
+import com.android.systemui.Dumpable
+import com.android.systemui.Flags
+import com.android.systemui.KairosActivatable
+import com.android.systemui.KairosBuilder
+import com.android.systemui.activated
+import com.android.systemui.broadcast.BroadcastDispatcher
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Background
+import com.android.systemui.dagger.qualifiers.Main
+import com.android.systemui.dump.DumpManager
+import com.android.systemui.kairos.BuildSpec
+import com.android.systemui.kairos.Events
+import com.android.systemui.kairos.ExperimentalKairosApi
+import com.android.systemui.kairos.Incremental
+import com.android.systemui.kairos.State
+import com.android.systemui.kairos.StateSelector
+import com.android.systemui.kairos.asIncremental
+import com.android.systemui.kairos.asyncEvent
+import com.android.systemui.kairos.buildSpec
+import com.android.systemui.kairos.changes
+import com.android.systemui.kairos.combine
+import com.android.systemui.kairos.effect
+import com.android.systemui.kairos.filterNotNull
+import com.android.systemui.kairos.flatMap
+import com.android.systemui.kairos.map
+import com.android.systemui.kairos.mapNotNull
+import com.android.systemui.kairos.mapValues
+import com.android.systemui.kairos.mergeLeft
+import com.android.systemui.kairos.onEach
+import com.android.systemui.kairos.rebuildOn
+import com.android.systemui.kairos.selector
+import com.android.systemui.kairos.stateOf
+import com.android.systemui.kairos.switchEvents
+import com.android.systemui.kairos.transitions
+import com.android.systemui.kairos.util.WithPrev
+import com.android.systemui.kairosBuilder
+import com.android.systemui.log.table.TableLogBuffer
+import com.android.systemui.log.table.TableLogBufferFactory
+import com.android.systemui.log.table.logDiffsForTable
+import com.android.systemui.res.R
+import com.android.systemui.statusbar.pipeline.airplane.data.repository.AirplaneModeRepository
+import com.android.systemui.statusbar.pipeline.dagger.MobileSummaryLog
+import com.android.systemui.statusbar.pipeline.mobile.data.MobileInputLogger
+import com.android.systemui.statusbar.pipeline.mobile.data.model.NetworkNameModel
+import com.android.systemui.statusbar.pipeline.mobile.data.model.SubscriptionModel
+import com.android.systemui.statusbar.pipeline.mobile.data.repository.CarrierConfigRepository
+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.util.MobileMappingsProxy
+import com.android.systemui.statusbar.pipeline.mobile.util.SubscriptionManagerProxy
+import com.android.systemui.statusbar.pipeline.shared.data.model.DefaultConnectionModel
+import com.android.systemui.statusbar.pipeline.shared.data.repository.ConnectivityRepository
+import com.android.systemui.statusbar.pipeline.wifi.data.repository.WifiRepository
+import com.android.systemui.statusbar.pipeline.wifi.shared.model.WifiNetworkModel
+import com.android.systemui.utils.coroutines.flow.conflatedCallbackFlow
+import dagger.Binds
+import dagger.Lazy
+import dagger.Provides
+import dagger.multibindings.ElementsIntoSet
+import java.io.PrintWriter
+import java.time.Duration
+import javax.inject.Inject
+import javax.inject.Provider
+import kotlinx.coroutines.CoroutineDispatcher
+import kotlinx.coroutines.channels.awaitClose
+import kotlinx.coroutines.flow.callbackFlow
+import kotlinx.coroutines.flow.flowOn
+import kotlinx.coroutines.flow.onStart
+import kotlinx.coroutines.withContext
+
+@ExperimentalKairosApi
+@SysUISingleton
+class MobileConnectionsRepositoryKairosImpl
+@Inject
+constructor(
+ connectivityRepository: ConnectivityRepository,
+ private val subscriptionManager: SubscriptionManager,
+ private val subscriptionManagerProxy: SubscriptionManagerProxy,
+ private val telephonyManager: TelephonyManager,
+ private val logger: MobileInputLogger,
+ @MobileSummaryLog private val tableLogger: TableLogBuffer,
+ mobileMappingsProxy: MobileMappingsProxy,
+ broadcastDispatcher: BroadcastDispatcher,
+ private val context: Context,
+ @Background private val bgDispatcher: CoroutineDispatcher,
+ @Main private val mainDispatcher: CoroutineDispatcher,
+ airplaneModeRepository: AirplaneModeRepository,
+ // Some "wifi networks" should be rendered as a mobile connection, which is why the wifi
+ // repository is an input to the mobile repository.
+ // See [CarrierMergedConnectionRepositoryKairos] for details.
+ wifiRepository: WifiRepository,
+ private val keyguardUpdateMonitor: KeyguardUpdateMonitor,
+ dumpManager: DumpManager,
+ private val mobileRepoFactory: Lazy<ConnectionRepoFactory>,
+) : MobileConnectionsRepositoryKairos, Dumpable, KairosBuilder by kairosBuilder() {
+
+ init {
+ dumpManager.registerNormalDumpable("MobileConnectionsRepositoryKairos", this)
+ }
+
+ private val carrierMergedSubId: State<Int?> = buildState {
+ combine(
+ wifiRepository.wifiNetwork.toState(),
+ connectivityRepository.defaultConnections.toState(),
+ airplaneModeRepository.isAirplaneMode.toState(),
+ ) { wifiNetwork, defaultConnections, isAirplaneMode ->
+ // The carrier merged connection should only be used if it's also the default
+ // connection or mobile connections aren't available because of airplane mode.
+ val defaultConnectionIsNonMobile =
+ defaultConnections.carrierMerged.isDefault ||
+ defaultConnections.wifi.isDefault ||
+ isAirplaneMode
+
+ if (wifiNetwork is WifiNetworkModel.CarrierMerged && defaultConnectionIsNonMobile) {
+ wifiNetwork.subscriptionId
+ } else {
+ null
+ }
+ }
+ .also {
+ logDiffsForTable(it, tableLogger, LOGGING_PREFIX, columnName = "carrierMergedSubId")
+ }
+ }
+
+ private val mobileSubscriptionsChangeEvent: Events<Unit> = buildEvents {
+ conflatedCallbackFlow {
+ val callback =
+ object : SubscriptionManager.OnSubscriptionsChangedListener() {
+ override fun onSubscriptionsChanged() {
+ logger.logOnSubscriptionsChanged()
+ trySend(Unit)
+ }
+ }
+ subscriptionManager.addOnSubscriptionsChangedListener(Runnable::run, callback)
+ awaitClose { subscriptionManager.removeOnSubscriptionsChangedListener(callback) }
+ }
+ .flowOn(bgDispatcher)
+ .toEvents()
+ }
+
+ /** Turn ACTION_SERVICE_STATE (for subId = -1) into an event */
+ private val serviceStateChangedEvent: Events<Unit> = buildEvents {
+ broadcastDispatcher
+ .broadcastFlow(IntentFilter(Intent.ACTION_SERVICE_STATE)) { intent, _ ->
+ val subId =
+ intent.getIntExtra(
+ SubscriptionManager.EXTRA_SUBSCRIPTION_INDEX,
+ INVALID_SUBSCRIPTION_ID,
+ )
+
+ // Only emit if the subId is not associated with an active subscription
+ if (subId == INVALID_SUBSCRIPTION_ID) {
+ Unit
+ }
+ }
+ .toEvents()
+ }
+
+ /** Eager flow to determine the device-based emergency calls only state */
+ override val isDeviceEmergencyCallCapable: State<Boolean> = buildState {
+ rebuildOn(serviceStateChangedEvent) { asyncEvent { doAnyModemsSupportEmergencyCalls() } }
+ .switchEvents()
+ .holdState(false)
+ .also {
+ logDiffsForTable(
+ it,
+ tableLogger,
+ LOGGING_PREFIX,
+ columnName = "deviceEmergencyOnly",
+ )
+ }
+ }
+
+ private suspend fun doAnyModemsSupportEmergencyCalls(): Boolean =
+ withContext(bgDispatcher) {
+ 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 =
+ context.packageManager.hasSystemFeature(PackageManager.FEATURE_TELEPHONY_CALLING)
+ if (isAutomotive && !hasFeatureCalling) {
+ return@withContext false
+ }
+
+ // 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)
+ .map { telephonyManager.getServiceStateForSlot(it) }
+ .any { it?.isEmergencyOnly == true }
+ }
+
+ /**
+ * State flow that emits the set of mobile data subscriptions, each represented by its own
+ * [SubscriptionModel].
+ */
+ override val subscriptions: State<List<SubscriptionModel>> = buildState {
+ rebuildOn(mergeLeft(mobileSubscriptionsChangeEvent, carrierMergedSubId.changes)) {
+ asyncEvent { fetchSubscriptionModels() }
+ }
+ .switchEvents()
+ .holdState(emptyList())
+ .also {
+ logDiffsForTable(it, tableLogger, LOGGING_PREFIX, columnName = "subscriptions")
+ }
+ }
+
+ val subscriptionsById: State<Map<Int, SubscriptionModel>> =
+ subscriptions.map { subs -> subs.associateBy { it.subscriptionId } }
+
+ override val mobileConnectionsBySubId: Incremental<Int, MobileConnectionRepositoryKairos> =
+ buildIncremental {
+ subscriptionsById
+ .asIncremental()
+ .mapValues { (subId, sub) -> mobileRepoFactory.get().create(subId) }
+ .applyLatestSpecForKey()
+ }
+
+ private val telephonyManagerState: State<Pair<Int?, Set<Int>>> = buildState {
+ callbackFlow {
+ val callback =
+ object :
+ TelephonyCallback(),
+ ActiveDataSubscriptionIdListener,
+ EmergencyCallbackModeListener {
+ override fun onActiveDataSubscriptionIdChanged(subId: Int) {
+ if (subId != INVALID_SUBSCRIPTION_ID) {
+ trySend { (_, set): Pair<Int?, Set<Int>> -> subId to set }
+ } else {
+ trySend { (_, set): Pair<Int?, Set<Int>> -> null to set }
+ }
+ }
+
+ override fun onCallbackModeStarted(
+ type: Int,
+ timerDuration: Duration,
+ subId: Int,
+ ) {
+ trySend { (id, set): Pair<Int?, Set<Int>> -> id to (set + type) }
+ }
+
+ override fun onCallbackModeRestarted(
+ type: Int,
+ timerDuration: Duration,
+ subId: Int,
+ ) {
+ // no-op
+ }
+
+ override fun onCallbackModeStopped(type: Int, reason: Int, subId: Int) {
+ trySend { (id, set): Pair<Int?, Set<Int>> -> id to (set - type) }
+ }
+ }
+ telephonyManager.registerTelephonyCallback(Runnable::run, callback)
+ awaitClose { telephonyManager.unregisterTelephonyCallback(callback) }
+ }
+ .flowOn(bgDispatcher)
+ .scanToState(null to emptySet())
+ }
+
+ override val activeMobileDataSubscriptionId: State<Int?> =
+ telephonyManagerState
+ .map { it.first }
+ .also {
+ onActivated {
+ logDiffsForTable(it, tableLogger, LOGGING_PREFIX, columnName = "activeSubId")
+ }
+ }
+
+ override val activeMobileDataRepository: State<MobileConnectionRepositoryKairos?> =
+ combine(activeMobileDataSubscriptionId, mobileConnectionsBySubId) { id, cache -> cache[id] }
+
+ override val defaultDataSubId: State<Int?> = buildState {
+ broadcastDispatcher
+ .broadcastFlow(
+ IntentFilter(TelephonyManager.ACTION_DEFAULT_DATA_SUBSCRIPTION_CHANGED)
+ ) { intent, _ ->
+ intent
+ .getIntExtra(PhoneConstants.SUBSCRIPTION_KEY, INVALID_SUBSCRIPTION_ID)
+ .takeIf { it != INVALID_SUBSCRIPTION_ID }
+ }
+ .onStart {
+ emit(
+ subscriptionManagerProxy.getDefaultDataSubscriptionId().takeIf {
+ it != INVALID_SUBSCRIPTION_ID
+ }
+ )
+ }
+ .toState(initialValue = null)
+ .also { logDiffsForTable(it, tableLogger, LOGGING_PREFIX, columnName = "defaultSubId") }
+ }
+
+ private val carrierConfigChangedEvent: Events<Unit> =
+ buildEvents {
+ broadcastDispatcher
+ .broadcastFlow(IntentFilter(CarrierConfigManager.ACTION_CARRIER_CONFIG_CHANGED))
+ .toEvents()
+ }
+ .onEach { logger.logActionCarrierConfigChanged() }
+
+ override val defaultDataSubRatConfig: State<Config> = buildState {
+ rebuildOn(mergeLeft(defaultDataSubId.changes, carrierConfigChangedEvent)) {
+ Config.readConfig(context).also { effect { logger.logDefaultDataSubRatConfig(it) } }
+ }
+ }
+
+ override val defaultMobileIconMapping: State<Map<String, MobileIconGroup>> = buildState {
+ defaultDataSubRatConfig
+ .map { mobileMappingsProxy.mapIconSets(it) }
+ .apply { observe { logger.logDefaultMobileIconMapping(it) } }
+ }
+
+ override val defaultMobileIconGroup: State<MobileIconGroup> = buildState {
+ defaultDataSubRatConfig
+ .map { mobileMappingsProxy.getDefaultIcons(it) }
+ .apply { observe { logger.logDefaultMobileIconGroup(it) } }
+ }
+
+ override val isAnySimSecure: State<Boolean> = buildState {
+ conflatedCallbackFlow {
+ val callback =
+ object : KeyguardUpdateMonitorCallback() {
+ override fun onSimStateChanged(subId: Int, slotId: Int, simState: Int) {
+ logger.logOnSimStateChanged()
+ trySend(keyguardUpdateMonitor.isSimPinSecure)
+ }
+ }
+ keyguardUpdateMonitor.registerCallback(callback)
+ awaitClose { keyguardUpdateMonitor.removeCallback(callback) }
+ }
+ .flowOn(mainDispatcher)
+ .toState(false)
+ .also {
+ logDiffsForTable(it, tableLogger, LOGGING_PREFIX, columnName = "isAnySimSecure")
+ }
+ }
+
+ private val defaultConnections: State<DefaultConnectionModel> = buildState {
+ connectivityRepository.defaultConnections.toState()
+ }
+
+ override val mobileIsDefault: State<Boolean> =
+ defaultConnections
+ .map { it.mobile.isDefault }
+ .also {
+ onActivated {
+ logDiffsForTable(
+ it,
+ tableLogger,
+ columnPrefix = LOGGING_PREFIX,
+ columnName = "mobileIsDefault",
+ )
+ }
+ }
+
+ override val hasCarrierMergedConnection: State<Boolean> =
+ carrierMergedSubId
+ .map { it != null }
+ .also {
+ onActivated {
+ logDiffsForTable(
+ it,
+ tableLogger,
+ columnPrefix = LOGGING_PREFIX,
+ columnName = "hasCarrierMergedConnection",
+ )
+ }
+ }
+
+ override val defaultConnectionIsValidated: State<Boolean> =
+ defaultConnections
+ .map { it.isValidated }
+ .also {
+ onActivated {
+ logDiffsForTable(
+ it,
+ tableLogger,
+ columnPrefix = LOGGING_PREFIX,
+ columnName = "defaultConnectionIsValidated",
+ )
+ }
+ }
+
+ /**
+ * Flow that tracks the active mobile data subscriptions. Emits `true` whenever the active data
+ * subscription Id changes but the subscription group remains the same. In these cases, we want
+ * to retain the previous subscription's validation status for up to 2s to avoid flickering the
+ * icon.
+ *
+ * TODO(b/265164432): we should probably expose all change events, not just same group
+ */
+ @SuppressLint("MissingPermission")
+ override val activeSubChangedInGroupEvent: Events<Unit> = buildEvents {
+ activeMobileDataSubscriptionId.transitions
+ .mapNotNull { (prevVal, newVal) ->
+ prevVal?.let { newVal?.let { WithPrev(prevVal, newVal) } }
+ }
+ .mapAsyncLatest { (prevVal, newVal) ->
+ if (isActiveSubChangeInGroup(prevVal, newVal)) Unit else null
+ }
+ .filterNotNull()
+ }
+
+ private suspend fun isActiveSubChangeInGroup(prevId: Int, newId: Int): Boolean =
+ withContext(bgDispatcher) {
+ val prevSub = subscriptionManager.getActiveSubscriptionInfo(prevId)?.groupUuid
+ val nextSub = subscriptionManager.getActiveSubscriptionInfo(newId)?.groupUuid
+ prevSub != null && prevSub == nextSub
+ }
+
+ private val isInEcmModeTopLevel: State<Boolean> =
+ telephonyManagerState.map { it.second.isNotEmpty() }
+
+ override val isInEcmMode: State<Boolean> =
+ isInEcmModeTopLevel.flatMap { isInEcm ->
+ if (isInEcm) {
+ stateOf(true)
+ } else {
+ mobileConnectionsBySubId.flatMap {
+ it.mapValues { it.value.isInEcmMode }.combine().map { it.values.any { it } }
+ }
+ }
+ }
+
+ /** Determines which subId is currently carrier-merged. */
+ val carrierMergedSelector: StateSelector<Int?> = carrierMergedSubId.selector()
+
+ private suspend fun fetchSubscriptionModels(): List<SubscriptionModel> =
+ withContext(bgDispatcher) {
+ subscriptionManager.completeActiveSubscriptionInfoList.map { it.toSubscriptionModel() }
+ }
+
+ private fun SubscriptionInfo.toSubscriptionModel(): SubscriptionModel =
+ SubscriptionModel(
+ subscriptionId = subscriptionId,
+ isOpportunistic = isOpportunistic,
+ isExclusivelyNonTerrestrial = isOnlyNonTerrestrialNetwork,
+ groupUuid = groupUuid,
+ carrierName = carrierName.toString(),
+ profileClass = profileClass,
+ )
+
+ private var dumpCache: DumpCache? = null
+
+ private data class DumpCache(val repos: Map<Int, FullMobileConnectionRepositoryKairos>)
+
+ override fun dump(pw: PrintWriter, args: Array<String>) {
+ val cache = dumpCache ?: return
+ val ipw = IndentingPrintWriter(pw, " ")
+ ipw.println("Connection cache:")
+
+ ipw.increaseIndent()
+ cache.repos.forEach { (subId, repo) -> ipw.println("$subId: $repo") }
+ ipw.decreaseIndent()
+
+ ipw.println("Connections (${cache.repos.size} total):")
+ ipw.increaseIndent()
+ cache.repos.values.forEach { it.dump(ipw) }
+ ipw.decreaseIndent()
+ }
+
+ fun interface ConnectionRepoFactory {
+ fun create(subId: Int): BuildSpec<MobileConnectionRepositoryKairos>
+ }
+
+ @dagger.Module
+ object Module {
+ @Provides
+ @ElementsIntoSet
+ fun kairosActivatable(
+ impl: Provider<MobileConnectionsRepositoryKairosImpl>
+ ): Set<@JvmSuppressWildcards KairosActivatable> =
+ if (Flags.statusBarMobileIconKairos()) setOf(impl.get()) else emptySet()
+ }
+
+ companion object {
+ private const val LOGGING_PREFIX = "Repo"
+ }
+}
+
+@ExperimentalKairosApi
+class MobileConnectionRepositoryKairosFactoryImpl
+@Inject
+constructor(
+ context: Context,
+ private val connectionsRepo: MobileConnectionsRepositoryKairosImpl,
+ private val logFactory: TableLogBufferFactory,
+ private val carrierConfigRepo: CarrierConfigRepository,
+ private val telephonyManager: TelephonyManager,
+ private val mobileRepoFactory: MobileConnectionRepositoryKairosImpl.Factory,
+ private val mergedRepoFactory: CarrierMergedConnectionRepositoryKairos.Factory,
+) : MobileConnectionsRepositoryKairosImpl.ConnectionRepoFactory {
+
+ private val networkNameSeparator: String =
+ context.getString(R.string.status_bar_network_name_separator)
+
+ private val defaultNetworkName =
+ NetworkNameModel.Default(
+ context.getString(com.android.internal.R.string.lockscreen_carrier_default)
+ )
+
+ override fun create(subId: Int): BuildSpec<MobileConnectionRepositoryKairos> = buildSpec {
+ activated {
+ val mobileLogger =
+ logFactory.getOrCreate(tableBufferLogName(subId), MOBILE_CONNECTION_BUFFER_SIZE)
+ val mobileRepo = activated {
+ mobileRepoFactory.create(
+ subId,
+ mobileLogger,
+ connectionsRepo.subscriptionsById.map { subs -> subs[subId] },
+ defaultNetworkName,
+ networkNameSeparator,
+ carrierConfigRepo.getOrCreateConfigForSubId(subId),
+ telephonyManager.createForSubscriptionId(subId),
+ )
+ }
+ FullMobileConnectionRepositoryKairos(
+ subId = subId,
+ tableLogBuffer = mobileLogger,
+ mobileRepo = mobileRepo,
+ carrierMergedRepoSpec =
+ buildSpec {
+ activated { mergedRepoFactory.build(subId, mobileLogger, mobileRepo) }
+ },
+ isCarrierMerged = connectionsRepo.carrierMergedSelector[subId],
+ )
+ }
+ }
+
+ companion object {
+ /** The buffer size to use for logging. */
+ private const val MOBILE_CONNECTION_BUFFER_SIZE = 100
+
+ /** Returns a log buffer name for a mobile connection with the given [subId]. */
+ fun tableBufferLogName(subId: Int): String = "MobileConnectionLog[$subId]"
+ }
+
+ @dagger.Module
+ interface Module {
+ @Binds
+ fun bindImpl(
+ impl: MobileConnectionRepositoryKairosFactoryImpl
+ ): MobileConnectionsRepositoryKairosImpl.ConnectionRepoFactory
+ }
+}
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
new file mode 100644
index 000000000000..a9399593973b
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconInteractorKairos.kt
@@ -0,0 +1,355 @@
+/*
+ * 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.internal.telephony.flags.Flags
+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.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
+
+@ExperimentalKairosApi
+interface MobileIconInteractorKairos {
+ /** The table log created for this connection */
+ val tableLogBuffer: TableLogBuffer
+
+ /** The current mobile data activity */
+ val activity: State<DataActivityModel>
+
+ /** See [MobileConnectionsRepository.mobileIsDefault]. */
+ val mobileIsDefault: State<Boolean>
+
+ /**
+ * True when telephony tells us that the data state is CONNECTED. See
+ * [android.telephony.TelephonyCallback.DataConnectionStateListener] for more details. We
+ * 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: State<Boolean>
+
+ /** True if we consider this connection to be in service, i.e. can make calls */
+ val isInService: State<Boolean>
+
+ /** True if this connection is emergency only */
+ val isEmergencyOnly: State<Boolean>
+
+ /** Observable for the data enabled state of this connection */
+ val isDataEnabled: State<Boolean>
+
+ /** True if the RAT icon should always be displayed and false otherwise. */
+ val alwaysShowDataRatIcon: State<Boolean>
+
+ /** Canonical representation of the current mobile signal strength as a triangle. */
+ val signalLevelIcon: State<SignalIconModel>
+
+ /** Observable for RAT type (network type) indicator */
+ val networkTypeIconGroup: State<NetworkTypeIconModel>
+
+ /** Whether or not to show the slice attribution */
+ val showSliceAttribution: State<Boolean>
+
+ /** True if this connection is satellite-based */
+ val isNonTerrestrial: State<Boolean>
+
+ /**
+ * Provider name for this network connection. The name can be one of 3 values:
+ * 1. The default network name, if one is configured
+ * 2. A derived name based off of the intent [ACTION_SERVICE_PROVIDERS_UPDATED]
+ * 3. Or, in the case where the repository sends us the default network name, we check for an
+ * override in [connectionInfo.operatorAlphaShort], a value that is derived from
+ * [ServiceState]
+ */
+ val networkName: State<NetworkNameModel>
+
+ /**
+ * Provider name for this network connection. The name can be one of 3 values:
+ * 1. The default network name, if one is configured
+ * 2. A name provided by the [SubscriptionModel] of this network connection
+ * 3. Or, in the case where the repository sends us the default network name, we check for an
+ * override in [connectionInfo.operatorAlphaShort], a value that is derived from
+ * [ServiceState]
+ *
+ * TODO(b/296600321): De-duplicate this field with [networkName] after determining the data
+ * provided is identical
+ */
+ val carrierName: State<String>
+
+ /** True if there is only one active subscription. */
+ 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: State<Boolean>
+
+ /** See [MobileIconsInteractor.isForceHidden]. */
+ val isForceHidden: State<Boolean>
+
+ /** See [MobileConnectionRepository.isAllowedDuringAirplaneMode]. */
+ val isAllowedDuringAirplaneMode: State<Boolean>
+
+ /** True when in carrier network change mode */
+ val carrierNetworkChangeActive: State<Boolean>
+}
+
+/** Interactor for a single mobile connection. This connection _should_ have one subscription ID */
+@ExperimentalKairosApi
+class MobileIconInteractorKairosImpl(
+ 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,
+ private val carrierIdOverrides: MobileIconCarrierIdOverrides =
+ MobileIconCarrierIdOverridesImpl(),
+) : MobileIconInteractorKairos, KairosBuilder by kairosBuilder() {
+ override val tableLogBuffer: TableLogBuffer
+ get() = connectionRepository.tableLogBuffer
+
+ override val activity: State<DataActivityModel>
+ get() = connectionRepository.dataActivityDirection
+
+ override val isDataEnabled: State<Boolean> = connectionRepository.dataEnabled
+
+ 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: State<Boolean> =
+ connectionRepository.carrierId.map { carrierIdOverrides.carrierIdEntryExists(it) }
+
+ override val networkName: State<NetworkNameModel> =
+ combine(connectionRepository.operatorAlphaShort, connectionRepository.networkName) {
+ operatorAlphaShort,
+ networkName ->
+ if (networkName is NetworkNameModel.Default && operatorAlphaShort != null) {
+ NetworkNameModel.IntentDerived(operatorAlphaShort)
+ } else {
+ networkName
+ }
+ }
+
+ override val carrierName: State<String> =
+ combine(connectionRepository.operatorAlphaShort, connectionRepository.carrierName) {
+ operatorAlphaShort,
+ networkName ->
+ if (networkName is NetworkNameModel.Default && operatorAlphaShort != null) {
+ operatorAlphaShort
+ } else {
+ networkName.name
+ }
+ }
+
+ /** What the mobile icon would be before carrierId overrides */
+ 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
+ }
+ }
+ }
+
+ 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.sample(),
+ networkType.name,
+ context.resources,
+ )
+ if (iconOverride > 0) {
+ OverriddenIcon(networkType, iconOverride)
+ } else {
+ DefaultIcon(networkType)
+ }
+ } else {
+ DefaultIcon(networkType)
+ }
+ }
+ .also { logDiffsForTable(it, tableLogBuffer = tableLogBuffer) }
+ }
+
+ override val showSliceAttribution: State<Boolean> =
+ combine(
+ connectionRepository.allowNetworkSliceIndicator,
+ connectionRepository.hasPrioritizedNetworkCapabilities,
+ ) { allowed, hasPrioritizedNetworkCapabilities ->
+ allowed && hasPrioritizedNetworkCapabilities
+ }
+
+ override val isNonTerrestrial: State<Boolean>
+ get() = connectionRepository.isNonTerrestrial
+
+ 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
+ }
+ }
+
+ 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
+ }
+ }
+
+ private val numberOfLevels: State<Int>
+ get() = connectionRepository.numberOfLevels
+
+ override val isDataConnected: State<Boolean> =
+ connectionRepository.dataConnectionState
+ .map { it == Connected }
+ .also {
+ onActivated { logDiffsForTable(it, tableLogBuffer, "icon", "isDataConnected") }
+ }
+
+ override val isInService
+ get() = connectionRepository.isInService
+
+ override val isEmergencyOnly: State<Boolean>
+ get() = connectionRepository.isEmergencyOnly
+
+ override val isAllowedDuringAirplaneMode: State<Boolean>
+ get() = connectionRepository.isAllowedDuringAirplaneMode
+
+ /** Whether or not to show the error state of [SignalDrawable] */
+ private val showExclamationMark: State<Boolean> =
+ combine(defaultSubscriptionHasDataEnabled, isDefaultConnectionFailed, isInService) {
+ isDefaultDataEnabled,
+ isDefaultConnectionFailed,
+ isInService ->
+ !isDefaultDataEnabled || isDefaultConnectionFailed || !isInService
+ }
+
+ private val cellularShownLevel: State<Int> =
+ combine(level, isInService, connectionRepository.inflateSignalStrength) {
+ level,
+ isInService,
+ inflate ->
+ when {
+ !isInService -> 0
+ inflate -> level + 1
+ else -> level
+ }
+ }
+
+ // Satellite level is unaffected by the inflateSignalStrength property
+ // See b/346904529 for details
+ private val satelliteShownLevel: State<Int> =
+ if (Flags.carrierRoamingNbIotNtn()) {
+ connectionRepository.satelliteLevel
+ } else {
+ combine(level, isInService) { level, isInService -> if (isInService) level else 0 }
+ }
+
+ private val cellularIcon: State<SignalIconModel.Cellular> =
+ combine(
+ cellularShownLevel,
+ numberOfLevels,
+ showExclamationMark,
+ carrierNetworkChangeActive,
+ ) { cellularShownLevel, numberOfLevels, showExclamationMark, carrierNetworkChange ->
+ SignalIconModel.Cellular(
+ cellularShownLevel,
+ numberOfLevels,
+ showExclamationMark,
+ carrierNetworkChange,
+ )
+ }
+
+ private val satelliteIcon: State<SignalIconModel.Satellite> =
+ satelliteShownLevel.map {
+ SignalIconModel.Satellite(
+ level = it,
+ icon =
+ SatelliteIconModel.fromSignalStrength(it)
+ ?: SatelliteIconModel.fromSignalStrength(0)!!,
+ )
+ }
+
+ override val signalLevelIcon: State<SignalIconModel> =
+ isNonTerrestrial
+ .flatMap { ntn ->
+ if (ntn) {
+ satelliteIcon
+ } else {
+ cellularIcon
+ }
+ }
+ .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
new file mode 100644
index 000000000000..14a276b60933
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconsInteractorKairos.kt
@@ -0,0 +1,449 @@
+/*
+ * 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 android.telephony.CarrierConfigManager
+import android.telephony.SubscriptionManager
+import android.telephony.SubscriptionManager.PROFILE_CLASS_PROVISIONING
+import com.android.settingslib.SignalIcon.MobileIconGroup
+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.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.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 dagger.Binds
+import dagger.Provides
+import dagger.multibindings.ElementsIntoSet
+import javax.inject.Inject
+import javax.inject.Provider
+import kotlin.time.Duration.Companion.seconds
+import kotlinx.coroutines.delay
+
+/**
+ * Business layer logic for the set of mobile subscription icons.
+ *
+ * This interactor represents known set of mobile subscriptions (represented by [SubscriptionInfo]).
+ * The list of subscriptions is filtered based on the opportunistic flags on the infos.
+ *
+ * It provides the default mapping between the telephony display info and the icon group that
+ * 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: State<Boolean>
+
+ /** List of subscriptions, potentially filtered for CBRS */
+ val filteredSubscriptions: State<List<SubscriptionModel>>
+
+ /**
+ * The current list of [MobileIconInteractor]s associated with the current list of
+ * [filteredSubscriptions]
+ */
+ val icons: Incremental<Int, MobileIconInteractorKairos>
+
+ /** Whether the mobile icons can be stacked vertically. */
+ val isStackable: State<Boolean>
+
+ /** True if the active mobile data subscription has data enabled */
+ val activeDataConnectionHasDataEnabled: State<Boolean>
+
+ /**
+ * Flow providing a reference to the Interactor for the active data subId. This represents the
+ * [MobileIconInteractorKairos] responsible for the active data connection, if any.
+ */
+ val activeDataIconInteractor: State<MobileIconInteractorKairos?>
+
+ /** True if the RAT icon should always be displayed and false otherwise. */
+ val alwaysShowDataRatIcon: State<Boolean>
+
+ /** True if the CDMA level should be preferred over the primary level. */
+ val alwaysUseCdmaLevel: State<Boolean>
+
+ /** True if there is only one active subscription. */
+ val isSingleCarrier: State<Boolean>
+
+ /** The icon mapping from network type to [MobileIconGroup] for the default subscription */
+ val defaultMobileIconMapping: State<Map<String, MobileIconGroup>>
+
+ /** Fallback [MobileIconGroup] in the case where there is no icon in the mapping */
+ val defaultMobileIconGroup: State<MobileIconGroup>
+
+ /** True only if the default network is mobile, and validation also failed */
+ val isDefaultConnectionFailed: State<Boolean>
+
+ /** True once the user has been set up */
+ val isUserSetUp: State<Boolean>
+
+ /** True if we're configured to force-hide the mobile icons and false otherwise. */
+ 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: State<Boolean>
+}
+
+@ExperimentalKairosApi
+@SysUISingleton
+class MobileIconsInteractorKairosImpl
+@Inject
+constructor(
+ private val mobileConnectionsRepo: MobileConnectionsRepositoryKairos,
+ private val carrierConfigTracker: CarrierConfigTracker,
+ @MobileSummaryLog private val tableLogger: TableLogBuffer,
+ connectivityRepository: ConnectivityRepository,
+ userSetupRepo: UserSetupRepository,
+ private val context: Context,
+ private val featureFlagsClassic: FeatureFlagsClassic,
+) : MobileIconsInteractorKairos, KairosBuilder by kairosBuilder() {
+
+ override val mobileIsDefault: State<Boolean> =
+ combine(
+ mobileConnectionsRepo.mobileIsDefault,
+ mobileConnectionsRepo.hasCarrierMergedConnection,
+ ) { mobileIsDefault, hasCarrierMergedConnection ->
+ // Because carrier merged networks are displayed as mobile networks, they're part of
+ // the `isDefault` calculation. See b/272586234.
+ mobileIsDefault || hasCarrierMergedConnection
+ }
+ .also {
+ onActivated {
+ logDiffsForTable(
+ it,
+ tableLogger,
+ LOGGING_PREFIX,
+ columnName = "mobileIsDefault",
+ )
+ }
+ }
+
+ 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: State<List<SubscriptionModel>> =
+ unfilteredSubscriptions.map {
+ it.asSequence().filterBasedOnProvisioning().filterBasedOnNtn().toList()
+ }
+
+ private fun Sequence<SubscriptionModel>.filterBasedOnProvisioning() =
+ if (!featureFlagsClassic.isEnabled(FILTER_PROVISIONING_NETWORK_SUBSCRIPTIONS)) {
+ this
+ } else {
+ filter { it.profileClass != PROFILE_CLASS_PROVISIONING }
+ }
+
+ /**
+ * Subscriptions that exclusively support non-terrestrial networks should **never** directly
+ * show any iconography in the status bar. These subscriptions only exist to provide a backing
+ * for the device-based satellite connections, and the iconography for those connections are
+ * already being handled in
+ * [com.android.systemui.statusbar.pipeline.satellite.data.DeviceBasedSatelliteRepository]. We
+ * need to filter out those subscriptions here so we guarantee the subscription never turns into
+ * an icon. See b/336881301.
+ */
+ private fun Sequence<SubscriptionModel>.filterBasedOnNtn(): Sequence<SubscriptionModel> =
+ filter {
+ !it.isExclusivelyNonTerrestrial
+ }
+
+ /**
+ * Generally, SystemUI wants to show iconography for each subscription that is listed by
+ * [SubscriptionManager]. However, in the case of opportunistic subscriptions, we want to only
+ * show a single representation of the pair of subscriptions. The docs define opportunistic as:
+ *
+ * "A subscription is opportunistic (if) the network it connects to has limited coverage"
+ * https://developer.android.com/reference/android/telephony/SubscriptionManager#setOpportunistic(boolean,%20int)
+ *
+ * In the case of opportunistic networks (typically CBRS), we will filter out one of the
+ * subscriptions based on
+ * [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: State<List<SubscriptionModel>> = buildState {
+ combine(
+ subscriptionsBasedFilteredSubs,
+ mobileConnectionsRepo.activeMobileDataSubscriptionId,
+ connectivityRepository.vcnSubId.toState(),
+ ) { preFilteredSubs, activeId, vcnSubId ->
+ filterSubsBasedOnOpportunistic(preFilteredSubs, activeId, vcnSubId)
+ }
+ .also {
+ logDiffsForTable(
+ it,
+ tableLogger,
+ LOGGING_PREFIX,
+ columnName = "filteredSubscriptions",
+ )
+ }
+ }
+
+ private fun filterSubsBasedOnOpportunistic(
+ subList: List<SubscriptionModel>,
+ activeId: Int?,
+ vcnSubId: Int?,
+ ): List<SubscriptionModel> {
+ // Based on the old logic,
+ if (subList.size != 2) {
+ return subList
+ }
+
+ val info1 = subList[0]
+ val info2 = subList[1]
+
+ // Filtering only applies to subscriptions in the same group
+ if (info1.groupUuid == null || info1.groupUuid != info2.groupUuid) {
+ return subList
+ }
+
+ // If both subscriptions are primary, show both
+ if (!info1.isOpportunistic && !info2.isOpportunistic) {
+ return subList
+ }
+
+ // NOTE: at this point, we are now returning a single SubscriptionInfo
+
+ // If carrier required, always show the icon of the primary subscription.
+ // Otherwise, show whichever subscription is currently active for internet.
+ if (carrierConfigTracker.alwaysShowPrimarySignalBarInOpportunisticNetworkDefault) {
+ // return the non-opportunistic info
+ return if (info1.isOpportunistic) listOf(info2) else listOf(info1)
+ } else {
+ // It's possible for the subId of the VCN to disagree with the active subId in
+ // cases where the system has tried to switch but found no connection. In these
+ // scenarios, VCN will always have the subId that we want to use, so use that
+ // value instead of the activeId reported by telephony
+ val subIdToKeep = vcnSubId ?: activeId
+
+ return if (info1.subscriptionId == subIdToKeep) {
+ listOf(info1)
+ } else {
+ listOf(info2)
+ }
+ }
+ }
+
+ 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 }
+ }
+ // Just map the repos to interactors
+ .mapValues { (subId, repo) -> buildSpec { mobileConnection(repo) } }
+ .applyLatestSpecForKey()
+ }
+
+ override val isStackable: State<Boolean> =
+ if (NewStatusBarIcons.isEnabled && StatusBarRootModernization.isEnabled) {
+ 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
+ // - They have the same number of levels
+ signalLevelIcons.filterIsInstance<SignalIconModel.Cellular>().let {
+ it.size == 2 && it[0].numberOfLevels == it[1].numberOfLevels
+ }
+ }
+ }
+ } 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
+ * validated bit from the old active network (A) while data is changing to the new one (B).
+ *
+ * This condition only applies if
+ * 1. A and B are in the same subscription group (e.g. for CBRS data switching) and
+ * 2. A was validated before the switch
+ *
+ * The goal of this is to minimize the flickering in the UI of the cellular indicator
+ */
+ private val forcingCellularValidation: State<Boolean> = buildState {
+ mobileConnectionsRepo.activeSubChangedInGroupEvent
+ .filter(mobileConnectionsRepo.defaultConnectionIsValidated)
+ .mapLatestBuild {
+ asyncEvent {
+ delay(2.seconds)
+ false
+ }
+ .holdState(true)
+ }
+ .holdState(stateOf(false))
+ .flatten()
+ .also {
+ logDiffsForTable(it, tableLogger, LOGGING_PREFIX, columnName = "forcingValidation")
+ }
+ }
+
+ /**
+ * 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: State<Map<String, MobileIconGroup>>
+ get() = mobileConnectionsRepo.defaultMobileIconMapping
+
+ override val alwaysShowDataRatIcon: State<Boolean> =
+ mobileConnectionsRepo.defaultDataSubRatConfig.map { it.alwaysShowDataRatIcon }
+
+ override val alwaysUseCdmaLevel: State<Boolean> =
+ mobileConnectionsRepo.defaultDataSubRatConfig.map { it.alwaysShowCdmaRssi }
+
+ override val isSingleCarrier: State<Boolean> =
+ mobileConnectionsRepo.subscriptions
+ .map { it.size == 1 }
+ .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: 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: State<Boolean> =
+ combine(
+ mobileIsDefault,
+ mobileConnectionsRepo.defaultConnectionIsValidated,
+ forcingCellularValidation,
+ ) { mobileIsDefault, defaultConnectionIsValidated, forcingCellularValidation ->
+ when {
+ !mobileIsDefault -> false
+ forcingCellularValidation -> false
+ else -> !defaultConnectionIsValidated
+ }
+ }
+ .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/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..c91ea9a50028 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,15 @@ fun StatusBarRoot(
setContent {
PlatformTheme {
- val chips by
+ val chipsVisibilityModel by
statusBarViewModel.ongoingActivityChips
.collectAsStateWithLifecycle()
- OngoingActivityChips(
- chips = chips,
- iconViewStore = iconViewStore,
- )
+ if (chipsVisibilityModel.areChipsAllowed) {
+ OngoingActivityChips(
+ chips = chipsVisibilityModel.chips,
+ iconViewStore = iconViewStore,
+ )
+ }
}
}
}
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..3bae91a0ebe3 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,6 +18,7 @@ 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.coroutines.launchTraced as launch
@@ -41,7 +42,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 +70,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 +130,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: StateFlow<ChipsVisibilityModel>
/**
* The multiple ongoing activity chips that should be shown on the left-hand side of the status
@@ -141,13 +146,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 +225,7 @@ constructor(
statusBarContentInsetsViewModelStore: StatusBarContentInsetsViewModelStore,
@Background bgScope: CoroutineScope,
@Background bgDispatcher: CoroutineDispatcher,
+ shadeDisplaysInteractor: Provider<ShadeDisplaysInteractor>,
) : HomeStatusBarViewModel, ExclusiveActivatable() {
private val hydrator = Hydrator(traceName = "HomeStatusBarViewModel.hydrator")
@@ -252,19 +257,49 @@ 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> =
+ /**
+ * 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 {
+ shadeInteractor.isAnyFullyExpanded
+ }
+
+ 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 +307,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 +315,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 +363,29 @@ 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.stateIn(
+ bgScope,
+ SharingStarted.WhileSubscribed(),
+ initialValue = false,
+ )
+
private val shouldHomeStatusBarBeVisible =
combine(
isHomeStatusBarAllowed,
@@ -369,15 +409,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 +450,38 @@ constructor(
combine(
isHomeStatusBarAllowed,
keyguardInteractor.isSecureCameraActive,
- headsUpNotificationInteractor.statusBarHeadsUpStatus,
- ) { isHomeStatusBarAllowed, isSecureCameraActive, headsUpState ->
- isHomeStatusBarAllowed && !isSecureCameraActive && !headsUpState.isPinned
+ hideStartSideContentForHeadsUp,
+ ) { isHomeStatusBarAllowed, isSecureCameraActive, hideStartSideContentForHeadsUp ->
+ isHomeStatusBarAllowed && !isSecureCameraActive && !hideStartSideContentForHeadsUp
+ }
+
+ override val ongoingActivityChips =
+ combine(ongoingActivityChipsViewModel.chips, canShowOngoingActivityChips) { chips, canShow
+ ->
+ ChipsVisibilityModel(chips, areChipsAllowed = canShow)
+ }
+ .stateIn(
+ bgScope,
+ SharingStarted.WhileSubscribed(),
+ initialValue =
+ ChipsVisibilityModel(
+ chips = MultipleOngoingActivityChipsModel(),
+ areChipsAllowed = false,
+ ),
+ )
+
+ private val hasOngoingActivityChips =
+ if (StatusBarChipsModernization.isEnabled) {
+ ongoingActivityChips.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> =
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/domain/interactor/ZenModeInteractor.kt b/packages/SystemUI/src/com/android/systemui/statusbar/policy/domain/interactor/ZenModeInteractor.kt
index a28d14fd908d..e8347df5653f 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
@@ -137,7 +137,7 @@ constructor(
)
else MutableStateFlow<ZenMode?>(null)
get() {
- ModesUi.assertInNewMode()
+ ModesUi.unsafeAssertInNewMode()
return field
}
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/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/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/util/kotlin/Producer.kt b/packages/SystemUI/src/com/android/systemui/util/kotlin/Producer.kt
new file mode 100644
index 000000000000..a6209fa72b53
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/util/kotlin/Producer.kt
@@ -0,0 +1,22 @@
+/*
+ * 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.util.kotlin
+
+/** Like a [javax.inject.Provider], but [get] is a `suspend fun`. */
+fun interface Producer<out T> {
+ suspend fun get(): T
+}
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/wallpapers/GradientColorWallpaper.kt b/packages/SystemUI/src/com/android/systemui/wallpapers/GradientColorWallpaper.kt
index 33e1929ebf8b..952d40ed63eb 100644
--- a/packages/SystemUI/src/com/android/systemui/wallpapers/GradientColorWallpaper.kt
+++ b/packages/SystemUI/src/com/android/systemui/wallpapers/GradientColorWallpaper.kt
@@ -17,8 +17,13 @@
package com.android.systemui.wallpapers
import android.app.Flags
+import android.content.res.Configuration.UI_MODE_NIGHT_MASK
+import android.content.res.Configuration.UI_MODE_NIGHT_YES
import android.graphics.Canvas
+import android.graphics.Color
import android.graphics.Paint
+import android.graphics.PorterDuff
+import android.graphics.PorterDuffXfermode
import android.graphics.RadialGradient
import android.graphics.Shader
import android.service.wallpaper.WallpaperService
@@ -74,9 +79,9 @@ class GradientColorWallpaper : WallpaperService() {
.toFloat()
val totalHeight = destRectF.height() + (offsetPx * 2)
val leftCenterX = -offsetPx
- val leftCenterY = -offsetPx
+ val leftCenterY = totalHeight - offsetPx
val rightCenterX = offsetPx + destRectF.width()
- val rightCenterY = totalHeight - offsetPx
+ val rightCenterY = -offsetPx
val radius = (destRectF.width() / 2) + offsetPx
canvas.drawCircle(
@@ -112,6 +117,28 @@ class GradientColorWallpaper : WallpaperService() {
)
},
)
+
+ val isDarkMode =
+ context.resources.configuration.uiMode and UI_MODE_NIGHT_MASK ==
+ UI_MODE_NIGHT_YES
+ val maskColor =
+ ColorUtils.setAlphaComponent(
+ if (isDarkMode) Color.BLACK else Color.WHITE,
+ /* alpha= */ 87, // 0.34f * 255
+ )
+ val maskPaint =
+ Paint().apply {
+ xfermode =
+ PorterDuffXfermode(
+ if (isDarkMode) {
+ PorterDuff.Mode.DARKEN
+ } else {
+ PorterDuff.Mode.LIGHTEN
+ }
+ )
+ color = maskColor
+ }
+ canvas.drawRect(destRectF, maskPaint)
} catch (exception: IllegalStateException) {
Log.d(TAG, "Fail to draw in the canvas", exception)
} finally {
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/ClockEventControllerTest.kt b/packages/SystemUI/tests/src/com/android/keyguard/ClockEventControllerTest.kt
index f822ee97807e..f18d73d57f83 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/ClockEventControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/keyguard/ClockEventControllerTest.kt
@@ -194,7 +194,7 @@ class ClockEventControllerTest : SysuiTestCase() {
@Test
fun clockSet_validateInitialization() {
- verify(clock).initialize(any(), anyFloat(), anyFloat())
+ verify(clock).initialize(any(), anyFloat(), anyFloat(), any())
}
@Test
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/TransitionAnimatorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/animation/TransitionAnimatorTest.kt
index a1f59c2cc2b5..1268de0f4141 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/animation/TransitionAnimatorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/animation/TransitionAnimatorTest.kt
@@ -23,11 +23,14 @@ 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
@@ -45,25 +48,29 @@ 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))
+ }
}
}
}
@@ -71,7 +78,44 @@ class TransitionAnimatorTest(
}
private val kosmos = Kosmos()
- private val pathManager = GoldenPathManager(context, GOLDENS_PATH, pathConfig = PathConfig())
+ 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/bluetooth/qsdialog/AudioSharingDeviceItemActionInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/bluetooth/qsdialog/AudioSharingDeviceItemActionInteractorTest.kt
index 9ae57153f3ef..2fc81eb9d66a 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/bluetooth/qsdialog/AudioSharingDeviceItemActionInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/bluetooth/qsdialog/AudioSharingDeviceItemActionInteractorTest.kt
@@ -231,6 +231,11 @@ class AudioSharingDeviceItemActionInteractorTest : SysuiTestCase() {
actionInteractorImpl.onActionIconClick(inAudioSharingMediaDeviceItem) {}
assertThat(bluetoothTileDialogAudioSharingRepository.audioSharingStarted)
.isEqualTo(false)
+ verify(bluetoothTileDialogLogger)
+ .logAudioSharingButtonClick(
+ AudioSharingButtonClick.CHECK_MARK,
+ inAudioSharingMediaDeviceItem,
+ )
}
}
}
@@ -243,6 +248,11 @@ class AudioSharingDeviceItemActionInteractorTest : SysuiTestCase() {
actionInteractorImpl.onActionIconClick(connectedAudioSharingMediaDeviceItem) {}
assertThat(bluetoothTileDialogAudioSharingRepository.audioSharingStarted)
.isEqualTo(true)
+ verify(bluetoothTileDialogLogger)
+ .logAudioSharingButtonClick(
+ AudioSharingButtonClick.PLUS_BUTTON,
+ connectedAudioSharingMediaDeviceItem,
+ )
}
}
}
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/clipboardoverlay/ClipboardOverlayControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/clipboardoverlay/ClipboardOverlayControllerTest.java
index 562481567536..5c893da45b8d 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;
@@ -60,7 +59,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;
@@ -102,7 +100,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 +149,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
}
/**
@@ -170,13 +165,13 @@ public class ClipboardOverlayControllerTest extends SysuiTestCase {
getFakeBroadcastDispatcher(),
mBroadcastSender,
mTimeoutHandler,
- mFeatureFlags,
mClipboardUtils,
mExecutor,
mClipboardImageLoader,
mClipboardTransitionExecutor,
mClipboardIndicationProvider,
- mUiEventLogger);
+ mUiEventLogger,
+ new ActionIntentCreator());
verify(mClipboardOverlayView).setCallbacks(mOverlayCallbacksCaptor.capture());
mCallbacks = mOverlayCallbacksCaptor.getValue();
}
@@ -193,7 +188,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 +202,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 +212,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 +224,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 +241,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/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/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/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/qs/panels/ui/compose/EditModeTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/panels/ui/compose/EditModeTest.kt
index 8c09b81744d7..e76be82c9340 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,6 +25,8 @@ 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
@@ -80,7 +82,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 +92,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 +101,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..021be3235ee1 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
@@ -85,7 +86,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 +105,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 +124,8 @@ class ResizingTest : SysuiTestCase() {
composeRule.waitForIdle()
composeRule
- .onNodeWithContentDescription("tileA")
+ .onAllNodesWithText("tileA")
+ .onFirst()
.performClick() // Select
.performTouchInput { // Tap on resizing handle
click(centerRight)
@@ -141,7 +145,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 +166,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 +187,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/recents/LauncherProxyServiceTest.kt b/packages/SystemUI/tests/src/com/android/systemui/recents/LauncherProxyServiceTest.kt
index 40547c2787ac..155059ea5ed9 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())
}
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..51bb38fa5ba9 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
@@ -54,6 +53,7 @@ import com.android.systemui.kosmos.testDispatcher
import com.android.systemui.kosmos.testScope
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 +96,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 +244,7 @@ class NotificationShadeWindowViewControllerTest(flags: FlagsParameterization) :
notificationLaunchAnimationInteractor,
featureFlagsClassic,
fakeClock,
- sysUIKeyEventHandler,
+ WindowRootViewKeyEventHandler({ sysUIKeyEventHandler }, falsingCollector),
quickSettingsController,
primaryBouncerInteractor,
alternateBouncerInteractor,
@@ -591,34 +590,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/shade/ShadeHeaderControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shade/ShadeHeaderControllerTest.kt
index f3af794f776b..1269b23b8a30 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/ShadeHeaderControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/ShadeHeaderControllerTest.kt
@@ -390,30 +390,12 @@ class ShadeHeaderControllerTest : SysuiTestCase() {
fun testControllersCreatedAndInitialized() {
verify(variableDateViewController).init()
- verify(batteryMeterViewController).init()
- verify(batteryMeterViewController).ignoreTunerUpdates()
-
val inOrder = Mockito.inOrder(mShadeCarrierGroupControllerBuilder)
inOrder.verify(mShadeCarrierGroupControllerBuilder).setShadeCarrierGroup(carrierGroup)
inOrder.verify(mShadeCarrierGroupControllerBuilder).build()
}
@Test
- fun batteryModeControllerCalledWhenQsExpandedFractionChanges() {
- whenever(qsBatteryModeController.getBatteryMode(Mockito.same(null), eq(0f)))
- .thenReturn(BatteryMeterView.MODE_ON)
- whenever(qsBatteryModeController.getBatteryMode(Mockito.same(null), eq(1f)))
- .thenReturn(BatteryMeterView.MODE_ESTIMATE)
- shadeHeaderController.qsVisible = true
-
- val times = 10
- repeat(times) { shadeHeaderController.qsExpandedFraction = it / (times - 1).toFloat() }
-
- verify(batteryMeterView).setPercentShowMode(BatteryMeterView.MODE_ON)
- verify(batteryMeterView).setPercentShowMode(BatteryMeterView.MODE_ESTIMATE)
- }
-
- @Test
fun testClockPivotLtr() {
val width = 200
whenever(clock.width).thenReturn(width)
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/events/SystemStatusAnimationSchedulerImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/events/SystemStatusAnimationSchedulerImplTest.kt
index 3c4aeccec2eb..f8b35a1af7d1 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/events/SystemStatusAnimationSchedulerImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/events/SystemStatusAnimationSchedulerImplTest.kt
@@ -29,6 +29,7 @@ import com.android.systemui.animation.AnimatorTestRule
import com.android.systemui.dump.DumpManager
import com.android.systemui.privacy.OngoingPrivacyChip
import com.android.systemui.statusbar.BatteryStatusChip
+import com.android.systemui.statusbar.core.NewStatusBarIcons
import com.android.systemui.statusbar.events.shared.model.SystemEventAnimationState
import com.android.systemui.statusbar.events.shared.model.SystemEventAnimationState.AnimatingIn
import com.android.systemui.statusbar.events.shared.model.SystemEventAnimationState.AnimatingOut
@@ -118,11 +119,11 @@ class SystemStatusAnimationSchedulerImplTest : SysuiTestCase() {
}
@Test
- fun testBatteryStatusEvent_standardAnimationLifecycle() = runTest {
+ fun testStatusEvent_standardAnimationLifecycle() = runTest {
// Instantiate class under test with TestScope from runTest
- initializeSystemStatusAnimationScheduler(testScope = this)
+ initializeSystemStatusAnimationScheduler(this)
- val batteryChip = createAndScheduleFakeBatteryEvent()
+ val eventChip = createAndScheduleFakeEvent()
// assert that animation is queued
assertEquals(AnimationQueued, systemStatusAnimationScheduler.animationState.value)
@@ -131,8 +132,7 @@ class SystemStatusAnimationSchedulerImplTest : SysuiTestCase() {
advanceTimeBy(DEBOUNCE_DELAY + 1)
// status chip starts animating in after debounce delay
assertEquals(AnimatingIn, systemStatusAnimationScheduler.animationState.value)
- assertEquals(0f, batteryChip.contentView.alpha)
- assertEquals(0f, batteryChip.view.alpha)
+ assertEquals(0f, eventChip.view.alpha)
verify(listener, times(1)).onSystemEventAnimationBegin()
// skip appear animation
@@ -140,23 +140,20 @@ class SystemStatusAnimationSchedulerImplTest : SysuiTestCase() {
advanceTimeBy(APPEAR_ANIMATION_DURATION)
// assert that status chip is visible
assertEquals(RunningChipAnim, systemStatusAnimationScheduler.animationState.value)
- assertEquals(1f, batteryChip.contentView.alpha)
- assertEquals(1f, batteryChip.view.alpha)
+ assertEquals(1f, eventChip.view.alpha)
// skip status chip display time
advanceTimeBy(DISPLAY_LENGTH + 1)
// assert that it is still visible but switched to the AnimatingOut state
assertEquals(AnimatingOut, systemStatusAnimationScheduler.animationState.value)
- assertEquals(1f, batteryChip.contentView.alpha)
- assertEquals(1f, batteryChip.view.alpha)
+ assertEquals(1f, eventChip.view.alpha)
verify(listener, times(1)).onSystemEventAnimationFinish(false)
// skip disappear animation
animatorTestRule.advanceTimeBy(DISAPPEAR_ANIMATION_DURATION)
// assert that it is not visible anymore
assertEquals(Idle, systemStatusAnimationScheduler.animationState.value)
- assertEquals(0f, batteryChip.contentView.alpha)
- assertEquals(0f, batteryChip.view.alpha)
+ assertEquals(0f, eventChip.view.alpha)
}
/** Regression test for b/294104969. */
@@ -226,8 +223,8 @@ class SystemStatusAnimationSchedulerImplTest : SysuiTestCase() {
initializeSystemStatusAnimationScheduler(testScope = this)
// create and schedule low priority event
- val batteryChip = createAndScheduleFakeBatteryEvent()
- batteryChip.view.alpha = 0f
+ val eventChip = createAndScheduleFakeEvent()
+ eventChip.view.alpha = 0f
// assert that animation is queued
assertEquals(AnimationQueued, systemStatusAnimationScheduler.animationState.value)
@@ -244,7 +241,7 @@ class SystemStatusAnimationSchedulerImplTest : SysuiTestCase() {
// high priority status chip is visible while low priority status chip is not visible
assertEquals(RunningChipAnim, systemStatusAnimationScheduler.animationState.value)
assertEquals(1f, privacyChip.view.alpha)
- assertEquals(0f, batteryChip.view.alpha)
+ assertEquals(0f, eventChip.view.alpha)
}
@Test
@@ -253,14 +250,14 @@ class SystemStatusAnimationSchedulerImplTest : SysuiTestCase() {
initializeSystemStatusAnimationScheduler(testScope = this)
// create and schedule low priority event
- val batteryChip = createAndScheduleFakeBatteryEvent()
+ val eventChip = createAndScheduleFakeEvent()
// fast forward to RunningChipAnim state
fastForwardAnimationToState(RunningChipAnim)
// assert that chip is displayed
assertEquals(RunningChipAnim, systemStatusAnimationScheduler.animationState.value)
- assertEquals(1f, batteryChip.view.alpha)
+ assertEquals(1f, eventChip.view.alpha)
// create and schedule high priority event
val privacyChip = createAndScheduleFakePrivacyEvent()
@@ -284,7 +281,7 @@ class SystemStatusAnimationSchedulerImplTest : SysuiTestCase() {
// high priority status chip is visible while low priority status chip is not visible
assertEquals(RunningChipAnim, systemStatusAnimationScheduler.animationState.value)
assertEquals(1f, privacyChip.view.alpha)
- assertEquals(0f, batteryChip.view.alpha)
+ assertEquals(0f, eventChip.view.alpha)
}
@Test
@@ -293,7 +290,7 @@ class SystemStatusAnimationSchedulerImplTest : SysuiTestCase() {
initializeSystemStatusAnimationScheduler(testScope = this)
// create and schedule low priority event
- val batteryChip = createAndScheduleFakeBatteryEvent()
+ val eventChip = createAndScheduleFakeEvent()
// skip debounce delay
advanceTimeBy(DEBOUNCE_DELAY + 1)
@@ -331,7 +328,7 @@ class SystemStatusAnimationSchedulerImplTest : SysuiTestCase() {
// high priority status chip is visible while low priority status chip is not visible
assertEquals(RunningChipAnim, systemStatusAnimationScheduler.animationState.value)
assertEquals(1f, privacyChip.view.alpha)
- assertEquals(0f, batteryChip.view.alpha)
+ assertEquals(0f, eventChip.view.alpha)
}
@Test
@@ -343,8 +340,8 @@ class SystemStatusAnimationSchedulerImplTest : SysuiTestCase() {
val privacyChip = createAndScheduleFakePrivacyEvent()
// create and schedule low priority event
- val batteryChip = createAndScheduleFakeBatteryEvent()
- batteryChip.view.alpha = 0f
+ val eventChip = createAndScheduleFakeEvent()
+ eventChip.view.alpha = 0f
// skip debounce delay and appear animation
advanceTimeBy(DEBOUNCE_DELAY + APPEAR_ANIMATION_DURATION + 1)
@@ -353,7 +350,7 @@ class SystemStatusAnimationSchedulerImplTest : SysuiTestCase() {
// high priority status chip is visible while low priority status chip is not visible
assertEquals(RunningChipAnim, systemStatusAnimationScheduler.animationState.value)
assertEquals(1f, privacyChip.view.alpha)
- assertEquals(0f, batteryChip.view.alpha)
+ assertEquals(0f, eventChip.view.alpha)
}
@Test
@@ -649,12 +646,17 @@ class SystemStatusAnimationSchedulerImplTest : SysuiTestCase() {
systemStatusAnimationScheduler.onStatusEvent(fakeEvent)
}
- private fun createAndScheduleFakeBatteryEvent(): BatteryStatusChip {
- val batteryChip = BatteryStatusChip(mContext)
- val fakeBatteryEvent =
- FakeStatusEvent(viewCreator = { batteryChip }, priority = 50, forceVisible = false)
- systemStatusAnimationScheduler.onStatusEvent(fakeBatteryEvent)
- return batteryChip
+ private fun createAndScheduleFakeEvent(): BackgroundAnimatableView {
+ val eventChip =
+ if (NewStatusBarIcons.isEnabled) {
+ BGImageView(mContext)
+ } else {
+ BatteryStatusChip(mContext)
+ }
+ val fakeStatusEvent =
+ FakeStatusEvent(viewCreator = { eventChip }, priority = 50, forceVisible = false)
+ systemStatusAnimationScheduler.onStatusEvent(fakeStatusEvent)
+ return eventChip
}
private fun initializeSystemStatusAnimationScheduler(
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/row/ExpandableNotificationRowTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowTest.java
index acfa94a0218b..4315c0f638ac 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
@@ -67,11 +67,13 @@ 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.SourceType;
import com.android.systemui.statusbar.notification.collection.NotificationEntry;
import com.android.systemui.statusbar.notification.headsup.PinnedStatus;
+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.wrapper.NotificationViewWrapper;
@@ -935,11 +937,13 @@ 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);
+ NotificationEntry entry = mock(NotificationEntry.class);
+ when(entry.isPromotedOngoing()).thenReturn(true);
+ row.setEntry(entry);
row.setSensitive(/* sensitive= */true, /* hideSensitive= */false);
row.setHideSensitiveForIntrinsicHeight(/* hideSensitive= */true);
@@ -948,11 +952,13 @@ 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);
+ NotificationEntry entry = mock(NotificationEntry.class);
+ when(entry.isPromotedOngoing()).thenReturn(true);
+ row.setEntry(entry);
row.setOnKeyguard(false);
// THEN
@@ -960,11 +966,13 @@ 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);
+ NotificationEntry entry = mock(NotificationEntry.class);
+ when(entry.isPromotedOngoing()).thenReturn(true);
+ row.setEntry(entry);
row.setOnKeyguard(true);
// THEN
@@ -972,12 +980,14 @@ 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);
+ NotificationEntry entry = mock(NotificationEntry.class);
+ when(entry.isPromotedOngoing()).thenReturn(true);
+ row.setEntry(entry);
row.setOnKeyguard(true);
row.setIgnoreLockscreenConstraints(true);
@@ -986,12 +996,14 @@ public class ExpandableNotificationRowTest extends SysuiTestCase {
}
@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);
+ NotificationEntry entry = mock(NotificationEntry.class);
+ when(entry.isPromotedOngoing()).thenReturn(true);
+ row.setEntry(entry);
row.setOnKeyguard(true);
row.setSaveSpaceOnLockscreen(true);
@@ -1000,12 +1012,14 @@ 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);
+ NotificationEntry entry = mock(NotificationEntry.class);
+ when(entry.isPromotedOngoing()).thenReturn(true);
+ row.setEntry(entry);
row.setOnKeyguard(true);
row.setSaveSpaceOnLockscreen(false);
@@ -1128,6 +1142,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();
+
+ 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/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/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/pipeline/mobile/data/model/SystemUiCarrierConfigTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/model/SystemUiCarrierConfigTest.kt
index df74404aaaf8..db948e947a26 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/model/SystemUiCarrierConfigTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/model/SystemUiCarrierConfigTest.kt
@@ -17,7 +17,6 @@
package com.android.systemui.statusbar.pipeline.mobile.data.model
import android.os.PersistableBundle
-import android.telephony.CarrierConfigManager
import android.telephony.CarrierConfigManager.KEY_INFLATE_SIGNAL_STRENGTH_BOOL
import android.telephony.CarrierConfigManager.KEY_SHOW_5G_SLICE_ICON_BOOL
import android.telephony.CarrierConfigManager.KEY_SHOW_OPERATOR_NAME_IN_STATUSBAR_BOOL
@@ -37,7 +36,7 @@ class SystemUiCarrierConfigTest : SysuiTestCase() {
@Before
fun setUp() {
- underTest = SystemUiCarrierConfig(SUB_1_ID, createTestConfig())
+ underTest = SystemUiCarrierConfig(SUB_1_ID, testCarrierConfig())
}
@Test
@@ -46,7 +45,7 @@ class SystemUiCarrierConfigTest : SysuiTestCase() {
assertThat(underTest.isUsingDefault).isTrue()
// ANY new config means we're no longer tracking defaults
- underTest.processNewCarrierConfig(createTestConfig())
+ underTest.processNewCarrierConfig(testCarrierConfig())
assertThat(underTest.isUsingDefault).isFalse()
}
@@ -58,7 +57,7 @@ class SystemUiCarrierConfigTest : SysuiTestCase() {
assertThat(underTest.allowNetworkSliceIndicator.value).isTrue()
underTest.processNewCarrierConfig(
- configWithOverrides(
+ testCarrierConfigWithOverrides(
KEY_INFLATE_SIGNAL_STRENGTH_BOOL to true,
KEY_SHOW_OPERATOR_NAME_IN_STATUSBAR_BOOL to true,
KEY_SHOW_5G_SLICE_ICON_BOOL to false,
@@ -81,11 +80,11 @@ class SystemUiCarrierConfigTest : SysuiTestCase() {
underTest =
SystemUiCarrierConfig(
SUB_1_ID,
- configWithOverrides(
+ testCarrierConfigWithOverrides(
KEY_INFLATE_SIGNAL_STRENGTH_BOOL to true,
KEY_SHOW_OPERATOR_NAME_IN_STATUSBAR_BOOL to true,
KEY_SHOW_5G_SLICE_ICON_BOOL to true,
- )
+ ),
)
assertThat(underTest.isUsingDefault).isTrue()
@@ -104,26 +103,5 @@ class SystemUiCarrierConfigTest : SysuiTestCase() {
companion object {
private const val SUB_1_ID = 1
-
- /**
- * In order to keep us from having to update every place that might want to create a config,
- * make sure to add new keys here
- */
- fun createTestConfig() =
- PersistableBundle().also {
- it.putBoolean(CarrierConfigManager.KEY_INFLATE_SIGNAL_STRENGTH_BOOL, false)
- it.putBoolean(CarrierConfigManager.KEY_SHOW_OPERATOR_NAME_IN_STATUSBAR_BOOL, false)
- it.putBoolean(CarrierConfigManager.KEY_SHOW_5G_SLICE_ICON_BOOL, true)
- }
-
- /** Override the default config with the given (key, value) pair */
- fun configWithOverride(key: String, override: Boolean): PersistableBundle =
- createTestConfig().also { it.putBoolean(key, override) }
-
- /** Override any number of configs from the default */
- fun configWithOverrides(vararg overrides: Pair<String, Boolean>) =
- createTestConfig().also { config ->
- overrides.forEach { (key, value) -> config.putBoolean(key, value) }
- }
}
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/CarrierConfigRepositoryImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/CarrierConfigRepositoryImplTest.kt
index d074fc256133..e1cc25972105 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/CarrierConfigRepositoryImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/CarrierConfigRepositoryImplTest.kt
@@ -26,7 +26,7 @@ import com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession
import com.android.systemui.SysuiTestCase
import com.android.systemui.dump.DumpManager
import com.android.systemui.statusbar.pipeline.mobile.data.MobileInputLogger
-import com.android.systemui.statusbar.pipeline.mobile.data.model.SystemUiCarrierConfigTest.Companion.createTestConfig
+import com.android.systemui.statusbar.pipeline.mobile.data.model.testCarrierConfig
import com.android.systemui.util.mockito.whenever
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.flow.launchIn
@@ -163,8 +163,8 @@ class CarrierConfigRepositoryImplTest : SysuiTestCase() {
private const val SUB_ID_1 = 1
private const val SUB_ID_2 = 2
- private val DEFAULT_CONFIG = createTestConfig()
- private val CONFIG_1 = createTestConfig()
- private val CONFIG_2 = createTestConfig()
+ private val DEFAULT_CONFIG = testCarrierConfig()
+ private val CONFIG_1 = testCarrierConfig()
+ private val CONFIG_2 = testCarrierConfig()
}
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionRepositoryTest.kt
index a51e919636ee..ed8be9b253ab 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionRepositoryTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionRepositoryTest.kt
@@ -86,8 +86,8 @@ import com.android.systemui.statusbar.pipeline.mobile.data.model.ResolvedNetwork
import com.android.systemui.statusbar.pipeline.mobile.data.model.ResolvedNetworkType.UnknownNetworkType
import com.android.systemui.statusbar.pipeline.mobile.data.model.SubscriptionModel
import com.android.systemui.statusbar.pipeline.mobile.data.model.SystemUiCarrierConfig
-import com.android.systemui.statusbar.pipeline.mobile.data.model.SystemUiCarrierConfigTest.Companion.configWithOverride
-import com.android.systemui.statusbar.pipeline.mobile.data.model.SystemUiCarrierConfigTest.Companion.createTestConfig
+import com.android.systemui.statusbar.pipeline.mobile.data.model.testCarrierConfig
+import com.android.systemui.statusbar.pipeline.mobile.data.model.testCarrierConfigWithOverride
import com.android.systemui.statusbar.pipeline.mobile.data.repository.MobileConnectionRepository.Companion.DEFAULT_NUM_LEVELS
import com.android.systemui.statusbar.pipeline.mobile.data.repository.prod.MobileTelephonyHelpers.signalStrength
import com.android.systemui.statusbar.pipeline.mobile.data.repository.prod.MobileTelephonyHelpers.telephonyDisplayInfo
@@ -129,7 +129,7 @@ class MobileConnectionRepositoryTest : SysuiTestCase() {
@Mock private lateinit var context: Context
private val mobileMappings = FakeMobileMappingsProxy()
- private val systemUiCarrierConfig = SystemUiCarrierConfig(SUB_1_ID, createTestConfig())
+ private val systemUiCarrierConfig = SystemUiCarrierConfig(SUB_1_ID, testCarrierConfig())
private val testDispatcher = UnconfinedTestDispatcher()
private val testScope = TestScope(testDispatcher)
@@ -1314,13 +1314,13 @@ class MobileConnectionRepositoryTest : SysuiTestCase() {
assertThat(latest).isEqualTo(DEFAULT_NUM_LEVELS)
systemUiCarrierConfig.processNewCarrierConfig(
- configWithOverride(KEY_INFLATE_SIGNAL_STRENGTH_BOOL, true)
+ testCarrierConfigWithOverride(KEY_INFLATE_SIGNAL_STRENGTH_BOOL, true)
)
assertThat(latest).isEqualTo(DEFAULT_NUM_LEVELS + 1)
systemUiCarrierConfig.processNewCarrierConfig(
- configWithOverride(KEY_INFLATE_SIGNAL_STRENGTH_BOOL, false)
+ testCarrierConfigWithOverride(KEY_INFLATE_SIGNAL_STRENGTH_BOOL, false)
)
assertThat(latest).isEqualTo(DEFAULT_NUM_LEVELS)
@@ -1336,13 +1336,13 @@ class MobileConnectionRepositoryTest : SysuiTestCase() {
assertThat(latest).isEqualTo(false)
systemUiCarrierConfig.processNewCarrierConfig(
- configWithOverride(KEY_INFLATE_SIGNAL_STRENGTH_BOOL, true)
+ testCarrierConfigWithOverride(KEY_INFLATE_SIGNAL_STRENGTH_BOOL, true)
)
assertThat(latest).isEqualTo(true)
systemUiCarrierConfig.processNewCarrierConfig(
- configWithOverride(KEY_INFLATE_SIGNAL_STRENGTH_BOOL, false)
+ testCarrierConfigWithOverride(KEY_INFLATE_SIGNAL_STRENGTH_BOOL, false)
)
assertThat(latest).isEqualTo(false)
@@ -1354,13 +1354,13 @@ class MobileConnectionRepositoryTest : SysuiTestCase() {
val latest by collectLastValue(underTest.allowNetworkSliceIndicator)
systemUiCarrierConfig.processNewCarrierConfig(
- configWithOverride(KEY_SHOW_5G_SLICE_ICON_BOOL, true)
+ testCarrierConfigWithOverride(KEY_SHOW_5G_SLICE_ICON_BOOL, true)
)
assertThat(latest).isTrue()
systemUiCarrierConfig.processNewCarrierConfig(
- configWithOverride(KEY_SHOW_5G_SLICE_ICON_BOOL, false)
+ testCarrierConfigWithOverride(KEY_SHOW_5G_SLICE_ICON_BOOL, false)
)
assertThat(latest).isFalse()
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionTelephonySmokeTests.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionTelephonySmokeTests.kt
index ec260fcc7a65..6f21e795532b 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionTelephonySmokeTests.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionTelephonySmokeTests.kt
@@ -41,7 +41,7 @@ import com.android.systemui.statusbar.pipeline.mobile.data.model.NetworkNameMode
import com.android.systemui.statusbar.pipeline.mobile.data.model.ResolvedNetworkType
import com.android.systemui.statusbar.pipeline.mobile.data.model.SubscriptionModel
import com.android.systemui.statusbar.pipeline.mobile.data.model.SystemUiCarrierConfig
-import com.android.systemui.statusbar.pipeline.mobile.data.model.SystemUiCarrierConfigTest
+import com.android.systemui.statusbar.pipeline.mobile.data.model.testCarrierConfig
import com.android.systemui.statusbar.pipeline.mobile.data.repository.prod.MobileTelephonyHelpers.getTelephonyCallbackForType
import com.android.systemui.statusbar.pipeline.mobile.data.repository.prod.MobileTelephonyHelpers.signalStrength
import com.android.systemui.statusbar.pipeline.mobile.util.FakeMobileMappingsProxy
@@ -105,11 +105,7 @@ class MobileConnectionTelephonySmokeTests : SysuiTestCase() {
@Mock private lateinit var subscriptionModel: StateFlow<SubscriptionModel?>
private val mobileMappings = FakeMobileMappingsProxy()
- private val systemUiCarrierConfig =
- SystemUiCarrierConfig(
- SUB_1_ID,
- SystemUiCarrierConfigTest.createTestConfig(),
- )
+ private val systemUiCarrierConfig = SystemUiCarrierConfig(SUB_1_ID, testCarrierConfig())
private val testDispatcher = UnconfinedTestDispatcher()
private val testScope = TestScope(testDispatcher)
@@ -185,12 +181,7 @@ class MobileConnectionTelephonySmokeTests : SysuiTestCase() {
val job = underTest.dataActivityDirection.onEach { latest = it }.launchIn(this)
assertThat(latest)
- .isEqualTo(
- DataActivityModel(
- hasActivityIn = true,
- hasActivityOut = true,
- )
- )
+ .isEqualTo(DataActivityModel(hasActivityIn = true, hasActivityOut = true))
displayInfoJob.cancel()
job.cancel()
@@ -209,7 +200,7 @@ class MobileConnectionTelephonySmokeTests : SysuiTestCase() {
connectionCallback.onDataConnectionStateChanged(
TelephonyManager.DATA_CONNECTED,
- 200 /* unused */
+ 200, /* unused */
)
flipActivity(100, activityCallback)
@@ -320,10 +311,7 @@ class MobileConnectionTelephonySmokeTests : SysuiTestCase() {
job.cancel()
}
- private fun flipActivity(
- times: Int,
- callback: DataActivityListener,
- ) {
+ private fun flipActivity(times: Int, callback: DataActivityListener) {
repeat(times) { index -> callback.onDataActivity(index % 4) }
}
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/wallpapers/GradientColorWallpaperTest.kt b/packages/SystemUI/tests/src/com/android/systemui/wallpapers/GradientColorWallpaperTest.kt
index b4fbaad6ab37..5f3442048fcd 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/wallpapers/GradientColorWallpaperTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/wallpapers/GradientColorWallpaperTest.kt
@@ -18,7 +18,6 @@ package com.android.systemui.wallpapers
import android.app.Flags
import android.content.Context
-import android.content.res.Resources
import android.graphics.Canvas
import android.graphics.Paint
import android.graphics.Rect
@@ -43,6 +42,7 @@ import org.mockito.Mock
import org.mockito.Mockito.spy
import org.mockito.MockitoAnnotations
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
@@ -61,23 +61,20 @@ class GradientColorWallpaperTest : SysuiTestCase() {
@Mock private lateinit var mockContext: Context
- @Mock private lateinit var mockResources: Resources
-
@Before
fun setUp() {
MockitoAnnotations.initMocks(this)
+ val spyResources = spy(context.resources)
+
whenever(surfaceHolder.surface).thenReturn(surface)
whenever(surfaceHolder.surfaceFrame).thenReturn(surfaceFrame)
whenever(surface.lockHardwareCanvas()).thenReturn(canvas)
whenever(mockContext.getColor(anyInt())).thenReturn(1)
- whenever(mockContext.resources).thenReturn(mockResources)
- whenever(
- mockResources.getDimensionPixelOffset(
- eq(R.dimen.gradient_color_wallpaper_center_offset)
- )
- )
- .thenReturn(OFFSET_PX)
+ whenever(mockContext.resources).thenReturn(spyResources)
+ doReturn(OFFSET_PX)
+ .`when`(spyResources)
+ .getDimensionPixelOffset(eq(R.dimen.gradient_color_wallpaper_center_offset))
}
private fun createGradientColorWallpaperEngine(): Engine {
@@ -106,7 +103,8 @@ class GradientColorWallpaperTest : SysuiTestCase() {
engine.onSurfaceRedrawNeeded(surfaceHolder)
- verify(canvas).drawRect(any<RectF>(), any<Paint>())
+ // One rect for the background, one rect for the foreground mask.
+ verify(canvas, times(2)).drawRect(any<RectF>(), any<Paint>())
verify(canvas, times(2)).drawCircle(anyFloat(), anyFloat(), anyFloat(), any<Paint>())
}
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..a978ecdb3534 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;
@@ -451,7 +449,6 @@ public class BubblesTest extends SysuiTestCase {
() -> mSelectedUserInteractor,
mUserTracker,
mNotificationShadeWindowModel,
- new FakeSettings(),
mKosmos::getCommunalInteractor,
mKosmos.getShadeLayoutParams()
);
@@ -464,8 +461,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;
@@ -624,7 +621,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();
@@ -2996,9 +2994,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/src/com/android/systemui/media/controls/ui/viewmodel/MediaRecsCardViewModel.kt b/packages/SystemUI/tests/utils/src/android/net/ConnectivityManagerKosmos.kt
index f1f7dc2195d5..516053d00ee2 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/ui/viewmodel/MediaRecsCardViewModel.kt
+++ b/packages/SystemUI/tests/utils/src/android/net/ConnectivityManagerKosmos.kt
@@ -14,18 +14,9 @@
* limitations under the License.
*/
-package com.android.systemui.media.controls.ui.viewmodel
+package android.net
-import com.android.systemui.animation.Expandable
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.util.mockito.mockFixture
-/** 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,
-)
+var Kosmos.connectivityManager: ConnectivityManager by mockFixture()
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/bluetooth/qsdialog/AudioSharingInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/bluetooth/qsdialog/AudioSharingInteractorKosmos.kt
index e0c0fbd7f033..bc8e62c40f92 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/bluetooth/qsdialog/AudioSharingInteractorKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/bluetooth/qsdialog/AudioSharingInteractorKosmos.kt
@@ -26,6 +26,7 @@ val Kosmos.audioSharingInteractor: AudioSharingInteractor by
applicationContext,
localBluetoothManager,
bluetoothTileDialogAudioSharingRepository,
+ bluetoothTileDialogLogger,
testDispatcher,
)
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/bluetooth/qsdialog/FakeAudioSharingRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/bluetooth/qsdialog/FakeAudioSharingRepository.kt
index c744eacfa3f4..0f6f19129dd3 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/bluetooth/qsdialog/FakeAudioSharingRepository.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/bluetooth/qsdialog/FakeAudioSharingRepository.kt
@@ -28,7 +28,7 @@ class FakeAudioSharingRepository : AudioSharingRepository {
private val mutableInAudioSharing: MutableStateFlow<Boolean> = MutableStateFlow(false)
- private val mutableAudioSourceStateUpdate = MutableSharedFlow<Unit>()
+ private val mutableAudioSourceStateUpdate = MutableSharedFlow<Unit>(replay = 1)
var sourceAdded: Boolean = false
private set
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/bouncer/domain/interactor/BouncerInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/bouncer/domain/interactor/BouncerInteractorKosmos.kt
index d27ecce89937..94d27f73aee7 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/bouncer/domain/interactor/BouncerInteractorKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/bouncer/domain/interactor/BouncerInteractorKosmos.kt
@@ -28,6 +28,7 @@ import com.android.systemui.kosmos.testScope
import com.android.systemui.log.sessionTracker
import com.android.systemui.power.domain.interactor.powerInteractor
import com.android.systemui.scene.domain.interactor.sceneBackInteractor
+import com.android.systemui.scene.domain.interactor.sceneInteractor
val Kosmos.bouncerInteractor by Fixture {
BouncerInteractor(
@@ -39,6 +40,7 @@ val Kosmos.bouncerInteractor by Fixture {
powerInteractor = powerInteractor,
uiEventLogger = uiEventLogger,
sessionTracker = sessionTracker,
+ sceneInteractor = sceneInteractor,
sceneBackInteractor = sceneBackInteractor,
configurationInteractor = configurationInteractor,
)
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/communal/ui/viewmodel/CommunalToDreamButtonViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/communal/ui/viewmodel/CommunalToDreamButtonViewModelKosmos.kt
deleted file mode 100644
index 43d3eb7b857a..000000000000
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/communal/ui/viewmodel/CommunalToDreamButtonViewModelKosmos.kt
+++ /dev/null
@@ -1,39 +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.ui.viewmodel
-
-import android.service.dream.dreamManager
-import com.android.internal.logging.uiEventLogger
-import com.android.systemui.communal.domain.interactor.communalPrefsInteractor
-import com.android.systemui.communal.domain.interactor.communalSettingsInteractor
-import com.android.systemui.kosmos.Kosmos
-import com.android.systemui.kosmos.testDispatcher
-import com.android.systemui.plugins.activityStarter
-import com.android.systemui.statusbar.policy.batteryController
-
-val Kosmos.communalToDreamButtonViewModel by
- Kosmos.Fixture {
- CommunalToDreamButtonViewModel(
- backgroundContext = testDispatcher,
- batteryController = batteryController,
- prefsInteractor = communalPrefsInteractor,
- settingsInteractor = communalSettingsInteractor,
- activityStarter = activityStarter,
- dreamManager = dreamManager,
- uiEventLogger = uiEventLogger,
- )
- }
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..663a85330f70 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
@@ -53,6 +53,7 @@ class FakeDisplayRepository @Inject constructor() : DisplayRepository {
MutableSharedFlow<DisplayRepository.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 +63,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 +84,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)
@@ -104,7 +124,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..aa23aa30b7bc 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,30 @@ 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.fakePerDisplayInstanceRepository by
+ Kosmos.Fixture {
+ PerDisplayInstanceRepositoryImpl(
+ debugName = "fakePerDisplayInstanceRepository",
+ instanceProvider = fakePerDisplayInstanceProviderWithTeardown,
+ testScope.backgroundScope,
+ displayRepository,
+ dumpManager,
+ )
+ }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/kairos/CollectLastValue.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/kairos/CollectLastValue.kt
new file mode 100644
index 000000000000..927209f84f1d
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/kairos/CollectLastValue.kt
@@ -0,0 +1,106 @@
+/*
+ * 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.
+ */
+
+@file:OptIn(ExperimentalCoroutinesApi::class)
+
+package com.android.systemui.kairos
+
+import com.android.systemui.coroutines.collectLastValue
+import kotlin.properties.ReadOnlyProperty
+import kotlin.reflect.KProperty
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.Job
+import kotlinx.coroutines.launch
+import kotlinx.coroutines.test.TestScope
+import kotlinx.coroutines.test.runCurrent
+
+/**
+ * Collect [state] in a new [Job] and return a getter for the collection of values collected.
+ *
+ * ```
+ * fun myTest() = runTest {
+ * // ...
+ * val values by collectValues(underTest.flow)
+ * assertThat(values).isEqualTo(listOf(expected1, expected2, ...))
+ * }
+ * ```
+ */
+@ExperimentalKairosApi
+fun <T> TestScope.collectLastValue(state: State<T>, kairosNetwork: KairosNetwork): KairosValue<T?> {
+ var value: T? = null
+ backgroundScope.launch { kairosNetwork.activateSpec { state.observe { value = it } } }
+ return KairosValueImpl {
+ runCurrent()
+ value
+ }
+}
+
+/**
+ * Collect [flow] in a new [Job] and return a getter for the collection of values collected.
+ *
+ * ```
+ * fun myTest() = runTest {
+ * // ...
+ * val values by collectValues(underTest.flow)
+ * assertThat(values).isEqualTo(listOf(expected1, expected2, ...))
+ * }
+ * ```
+ */
+@ExperimentalKairosApi
+fun <T> TestScope.collectLastValue(flow: Events<T>, kairosNetwork: KairosNetwork): KairosValue<T?> {
+ var value: T? = null
+ backgroundScope.launch { kairosNetwork.activateSpec { flow.observe { value = it } } }
+ return KairosValueImpl {
+ runCurrent()
+ value
+ }
+}
+
+/**
+ * Collect [flow] in a new [Job] and return a getter for the collection of values collected.
+ *
+ * ```
+ * fun myTest() = runTest {
+ * // ...
+ * val values by collectValues(underTest.flow)
+ * assertThat(values).isEqualTo(listOf(expected1, expected2, ...))
+ * }
+ * ```
+ */
+@ExperimentalKairosApi
+fun <T> TestScope.collectValues(
+ flow: Events<T>,
+ kairosNetwork: KairosNetwork,
+): KairosValue<List<T>> {
+ val values = mutableListOf<T>()
+ backgroundScope.launch { kairosNetwork.activateSpec { flow.observe { values.add(it) } } }
+ return KairosValueImpl {
+ runCurrent()
+ values.toList()
+ }
+}
+
+/** @see collectLastValue */
+interface KairosValue<T> : ReadOnlyProperty<Any?, T> {
+ val value: T
+}
+
+private class KairosValueImpl<T>(private val block: () -> T) : KairosValue<T> {
+ override val value: T
+ get() = block()
+
+ override fun getValue(thisRef: Any?, property: KProperty<*>): T = value
+}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/kairos/KairosKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/kairos/KairosKosmos.kt
new file mode 100644
index 000000000000..d7f204183467
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/kairos/KairosKosmos.kt
@@ -0,0 +1,69 @@
+/*
+ * 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.kairos
+
+import com.android.systemui.KairosActivatable
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.Kosmos.Fixture
+import com.android.systemui.kosmos.applicationCoroutineScope
+import com.android.systemui.kosmos.testScope
+import kotlin.time.Duration
+import kotlin.time.Duration.Companion.seconds
+import kotlinx.coroutines.launch
+import kotlinx.coroutines.test.TestScope
+import kotlinx.coroutines.test.runTest
+
+@ExperimentalKairosApi
+val Kosmos.kairos: KairosNetwork by Fixture { applicationCoroutineScope.launchKairosNetwork() }
+
+@ExperimentalKairosApi
+fun Kosmos.activateKairosActivatable(activatable: KairosActivatable) {
+ applicationCoroutineScope.launch { kairos.activateSpec { activatable.run { activate() } } }
+}
+
+@ExperimentalKairosApi
+fun <T : KairosActivatable> ActivatedKairosFixture(block: Kosmos.() -> T) = Fixture {
+ block().also { activateKairosActivatable(it) }
+}
+
+@ExperimentalKairosApi
+fun Kosmos.runKairosTest(timeout: Duration = 5.seconds, block: suspend KairosTestScope.() -> Unit) =
+ testScope.runTest(timeout) { KairosTestScopeImpl(this@runKairosTest, this, kairos).block() }
+
+@ExperimentalKairosApi
+interface KairosTestScope : Kosmos {
+ fun <T> State<T>.collectLastValue(): KairosValue<T?>
+
+ suspend fun <T> State<T>.sample(): T
+
+ fun <T : KairosActivatable> T.activated(): T
+}
+
+@ExperimentalKairosApi
+private class KairosTestScopeImpl(
+ kosmos: Kosmos,
+ val testScope: TestScope,
+ val kairos: KairosNetwork,
+) : KairosTestScope, Kosmos by kosmos {
+ override fun <T> State<T>.collectLastValue(): KairosValue<T?> =
+ testScope.collectLastValue(this@collectLastValue, kairos)
+
+ override suspend fun <T> State<T>.sample(): T = kairos.transact { sample() }
+
+ override fun <T : KairosActivatable> T.activated(): T =
+ this.also { activateKairosActivatable(it) }
+}
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/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/GlanceableHubToPrimaryBouncerTransitionViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/GlanceableHubToPrimaryBouncerTransitionViewModelKosmos.kt
index b233d3ff9e0f..c6f55f053d35 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/GlanceableHubToPrimaryBouncerTransitionViewModelKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/GlanceableHubToPrimaryBouncerTransitionViewModelKosmos.kt
@@ -16,16 +16,20 @@
package com.android.systemui.keyguard.ui.viewmodel
+import com.android.systemui.communal.domain.interactor.communalSceneInteractor
import com.android.systemui.communal.domain.interactor.communalSettingsInteractor
import com.android.systemui.keyguard.ui.keyguardTransitionAnimationFlow
import com.android.systemui.keyguard.ui.transitions.blurConfig
import com.android.systemui.kosmos.Kosmos
import com.android.systemui.kosmos.Kosmos.Fixture
+import com.android.systemui.statusbar.policy.keyguardStateController
val Kosmos.glanceableHubToPrimaryBouncerTransitionViewModel by Fixture {
GlanceableHubToPrimaryBouncerTransitionViewModel(
animationFlow = keyguardTransitionAnimationFlow,
blurConfig = blurConfig,
communalSettingsInteractor = communalSettingsInteractor,
+ communalSceneInteractor = communalSceneInteractor,
+ keyguardStateController = keyguardStateController,
)
}
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 02cf1f5a7214..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
@@ -29,6 +29,7 @@ import com.android.systemui.common.ui.domain.interactor.configurationInteractor
import com.android.systemui.communal.data.repository.fakeCommunalSceneRepository
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.ui.viewmodel.communalTransitionViewModel
import com.android.systemui.concurrency.fakeExecutor
import com.android.systemui.deviceentry.domain.interactor.deviceEntryInteractor
@@ -52,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
@@ -63,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
@@ -87,6 +92,7 @@ import com.android.systemui.statusbar.pipeline.wifi.domain.interactor.wifiIntera
import com.android.systemui.statusbar.policy.configurationController
import com.android.systemui.statusbar.policy.data.repository.fakeDeviceProvisioningRepository
import com.android.systemui.statusbar.policy.domain.interactor.deviceProvisioningInteractor
+import com.android.systemui.statusbar.policy.keyguardStateController
import com.android.systemui.statusbar.ui.viewmodel.keyguardStatusBarViewModel
import com.android.systemui.util.time.systemClock
import com.android.systemui.volume.domain.interactor.volumeDialogInteractor
@@ -126,6 +132,7 @@ class KosmosJavaAdapter() {
val keyguardInteractor by lazy { kosmos.keyguardInteractor }
val keyguardTransitionRepository by lazy { kosmos.fakeKeyguardTransitionRepository }
val keyguardTransitionInteractor by lazy { kosmos.keyguardTransitionInteractor }
+ val keyguardStateController by lazy { kosmos.keyguardStateController }
val keyguardStatusBarViewModel by lazy { kosmos.keyguardStatusBarViewModel }
val powerRepository by lazy { kosmos.fakePowerRepository }
val clock by lazy { kosmos.systemClock }
@@ -147,6 +154,7 @@ class KosmosJavaAdapter() {
val deviceUnlockedInteractor by lazy { kosmos.deviceUnlockedInteractor }
val communalInteractor by lazy { kosmos.communalInteractor }
val communalSceneInteractor by lazy { kosmos.communalSceneInteractor }
+ val communalSettingsInteractor by lazy { kosmos.communalSettingsInteractor }
val sceneContainerPlugin by lazy { kosmos.sceneContainerPlugin }
val deviceProvisioningInteractor by lazy { kosmos.deviceProvisioningInteractor }
val fakeDeviceProvisioningRepository by lazy { kosmos.fakeDeviceProvisioningRepository }
@@ -194,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..b41ceff5f581 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,103 @@
* 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
+import java.util.concurrent.Callable
-/** 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 [loggingCallable] makes a call to [Log.wtf] */
@JvmOverloads
-fun assertLogsWtf(
+fun <T> assertLogsWtf(
message: String = "Expected Log.wtf to be called",
allowMultiple: Boolean = false,
- loggingRunnable: Runnable,
-): TerribleFailureLog =
- assertLogsWtf(message = message, allowMultiple = allowMultiple) { loggingRunnable.run() }
+ loggingCallable: Callable<T>,
+): WtfBlockResult<T> =
+ assertLogsWtf(message = message, allowMultiple = allowMultiple, loggingCallable::call)
-fun assertLogsWtfs(
+/** 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: () -> Unit,
-): TerribleFailureLog = assertLogsWtf(message, allowMultiple = true, loggingBlock)
+ loggingBlock: () -> T,
+): WtfBlockResult<T> = assertLogsWtf(message, allowMultiple = true, loggingBlock)
+/** Assert that [loggingCallable] makes at least one call to [Log.wtf] */
@JvmOverloads
-fun assertLogsWtfs(
+fun <T> assertLogsWtfs(
message: String = "Expected Log.wtf to be called once or more",
- loggingRunnable: Runnable,
-): TerribleFailureLog = assertLogsWtfs(message) { loggingRunnable.run() }
+ loggingCallable: Callable<T>,
+): WtfBlockResult<T> = assertLogsWtf(message, allowMultiple = true, loggingCallable)
/** The data passed to [TerribleFailureHandler.onTerribleFailure] */
data class TerribleFailureLog(
val tag: String,
val failure: Log.TerribleFailure,
- val system: Boolean
+ 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 assertRunnableLogsWtf(
+ message: String = "Expected Log.wtf to be called",
+ allowMultiple: Boolean = false,
+ loggingRunnable: Runnable,
+): WtfBlockResult<Unit> =
+ assertLogsWtf(message = message, allowMultiple = allowMultiple) { loggingRunnable.run() }
+
+/** Assert that [loggingRunnable] makes at least one call to [Log.wtf] */
+@JvmOverloads
+fun assertRunnableLogsWtfs(
+ message: String = "Expected Log.wtf to be called once or more",
+ loggingRunnable: Runnable,
+): 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/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/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/row/ui/viewmodel/ActivatableNotificationViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/row/ui/viewmodel/ActivatableNotificationViewModelKosmos.kt
index 2523975c182c..7ccbdb79101c 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/row/ui/viewmodel/ActivatableNotificationViewModelKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/row/ui/viewmodel/ActivatableNotificationViewModelKosmos.kt
@@ -19,9 +19,11 @@ package com.android.systemui.statusbar.notification.row.ui.viewmodel
import com.android.systemui.accessibility.domain.interactor.accessibilityInteractor
import com.android.systemui.kosmos.Kosmos
import com.android.systemui.kosmos.Kosmos.Fixture
+import com.android.systemui.window.domain.interactor.windowRootViewBlurInteractor
val Kosmos.activatableNotificationViewModel by Fixture {
ActivatableNotificationViewModel.invoke(
a11yInteractor = accessibilityInteractor,
+ windowRootViewBlurInteractor = windowRootViewBlurInteractor,
)
}
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/stack/ui/viewmodel/NotificationScrollViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationScrollViewModelKosmos.kt
index 167b11da9dba..87ce501b0402 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationScrollViewModelKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationScrollViewModelKosmos.kt
@@ -16,6 +16,7 @@
package com.android.systemui.statusbar.notification.stack.ui.viewmodel
+import com.android.systemui.bouncer.domain.interactor.bouncerInteractor
import com.android.systemui.dump.dumpManager
import com.android.systemui.keyguard.domain.interactor.keyguardInteractor
import com.android.systemui.kosmos.Kosmos
@@ -32,6 +33,7 @@ val Kosmos.notificationScrollViewModel by Fixture {
stackAppearanceInteractor = notificationStackAppearanceInteractor,
shadeInteractor = shadeInteractor,
shadeModeInteractor = shadeModeInteractor,
+ bouncerInteractor = bouncerInteractor,
remoteInputInteractor = remoteInputInteractor,
sceneInteractor = sceneInteractor,
keyguardInteractor = { keyguardInteractor },
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModelKosmos.kt
index 17ef208fe12e..85fe3d9a44ce 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModelKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModelKosmos.kt
@@ -17,6 +17,7 @@
package com.android.systemui.statusbar.notification.stack.ui.viewmodel
import android.content.applicationContext
+import com.android.systemui.bouncer.domain.interactor.bouncerInteractor
import com.android.systemui.common.ui.domain.interactor.configurationInteractor
import com.android.systemui.communal.domain.interactor.communalSceneInteractor
import com.android.systemui.dump.dumpManager
@@ -74,6 +75,7 @@ val Kosmos.sharedNotificationContainerViewModel by Fixture {
keyguardInteractor = keyguardInteractor,
keyguardTransitionInteractor = keyguardTransitionInteractor,
shadeInteractor = shadeInteractor,
+ bouncerInteractor = bouncerInteractor,
shadeModeInteractor = shadeModeInteractor,
notificationStackAppearanceInteractor = notificationStackAppearanceInteractor,
alternateBouncerToGoneTransitionViewModel = alternateBouncerToGoneTransitionViewModel,
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/data/model/SystemUiCarrierConfigFakes.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/pipeline/mobile/data/model/SystemUiCarrierConfigFakes.kt
new file mode 100644
index 000000000000..13b016342c15
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/pipeline/mobile/data/model/SystemUiCarrierConfigFakes.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.statusbar.pipeline.mobile.data.model
+
+import android.os.PersistableBundle
+import android.telephony.CarrierConfigManager
+
+/**
+ * In order to keep us from having to update every place that might want to create a config, make
+ * sure to add new keys here
+ */
+fun testCarrierConfig() =
+ PersistableBundle().also {
+ it.putBoolean(CarrierConfigManager.KEY_INFLATE_SIGNAL_STRENGTH_BOOL, false)
+ it.putBoolean(CarrierConfigManager.KEY_SHOW_OPERATOR_NAME_IN_STATUSBAR_BOOL, false)
+ it.putBoolean(CarrierConfigManager.KEY_SHOW_5G_SLICE_ICON_BOOL, true)
+ }
+
+/** Override the default config with the given (key, value) pair */
+fun testCarrierConfigWithOverride(key: String, override: Boolean): PersistableBundle =
+ testCarrierConfig().also { it.putBoolean(key, override) }
+
+/** Override any number of configs from the default */
+fun testCarrierConfigWithOverrides(vararg overrides: Pair<String, Boolean>) =
+ testCarrierConfig().also { config ->
+ overrides.forEach { (key, value) -> config.putBoolean(key, value) }
+ }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/FakeMobileConnectionRepositoryKairos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/FakeMobileConnectionRepositoryKairos.kt
new file mode 100644
index 000000000000..8cf3ee8d3c1c
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/FakeMobileConnectionRepositoryKairos.kt
@@ -0,0 +1,87 @@
+/*
+ * 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.data.repository
+
+import com.android.systemui.kairos.ExperimentalKairosApi
+import com.android.systemui.kairos.KairosNetwork
+import com.android.systemui.kairos.MutableState
+import com.android.systemui.kairos.State
+import com.android.systemui.log.table.TableLogBuffer
+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
+import com.android.systemui.statusbar.pipeline.mobile.data.repository.FakeMobileConnectionRepository.Companion.DEFAULT_NETWORK_NAME
+import com.android.systemui.statusbar.pipeline.mobile.data.repository.MobileConnectionRepository.Companion.DEFAULT_NUM_LEVELS
+import com.android.systemui.statusbar.pipeline.shared.data.model.DataActivityModel
+
+@ExperimentalKairosApi
+class FakeMobileConnectionRepositoryKairos(
+ override val subId: Int,
+ kairos: KairosNetwork,
+ override val tableLogBuffer: TableLogBuffer,
+) : MobileConnectionRepositoryKairos {
+ override val carrierId = MutableState(kairos, 0)
+ override val inflateSignalStrength = MutableState(kairos, false)
+ override val allowNetworkSliceIndicator = MutableState(kairos, true)
+ override val isEmergencyOnly = MutableState(kairos, false)
+ override val isRoaming = MutableState(kairos, false)
+ override val operatorAlphaShort = MutableState<String?>(kairos, null)
+ override val isInService = MutableState(kairos, false)
+ override val isNonTerrestrial = MutableState(kairos, false)
+ override val isGsm = MutableState(kairos, false)
+ override val cdmaLevel = MutableState(kairos, 0)
+ override val primaryLevel = MutableState(kairos, 0)
+ override val satelliteLevel = MutableState(kairos, 0)
+ override val dataConnectionState = MutableState(kairos, DataConnectionState.Disconnected)
+ override val dataActivityDirection =
+ MutableState(kairos, DataActivityModel(hasActivityIn = false, hasActivityOut = false))
+ override val carrierNetworkChangeActive = MutableState(kairos, false)
+ override val resolvedNetworkType =
+ MutableState<ResolvedNetworkType>(kairos, ResolvedNetworkType.UnknownNetworkType)
+ override val numberOfLevels = MutableState(kairos, DEFAULT_NUM_LEVELS)
+ override val dataEnabled = MutableState(kairos, true)
+ override val cdmaRoaming = MutableState(kairos, false)
+ override val networkName =
+ MutableState<NetworkNameModel>(kairos, NetworkNameModel.Default(DEFAULT_NETWORK_NAME))
+ override val carrierName =
+ MutableState<NetworkNameModel>(kairos, NetworkNameModel.Default(DEFAULT_NETWORK_NAME))
+ override val isAllowedDuringAirplaneMode = MutableState(kairos, false)
+ override val hasPrioritizedNetworkCapabilities = MutableState(kairos, false)
+ override val isInEcmMode: State<Boolean> = MutableState(kairos, false)
+
+ /**
+ * Set [primaryLevel] and [cdmaLevel]. Convenient when you don't care about the connection type
+ */
+ fun setAllLevels(level: Int) {
+ cdmaLevel.setValue(level)
+ primaryLevel.setValue(level)
+ }
+
+ /** Set the correct [resolvedNetworkType] for the given group via its lookup key */
+ fun setNetworkTypeKey(key: String) {
+ resolvedNetworkType.setValue(ResolvedNetworkType.DefaultNetworkType(key))
+ }
+
+ /**
+ * Set both [isRoaming] and [cdmaRoaming] properties, in the event that you don't care about the
+ * connection type
+ */
+ fun setAllRoaming(roaming: Boolean) {
+ isRoaming.setValue(roaming)
+ cdmaRoaming.setValue(roaming)
+ }
+}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/FakeMobileConnectionsRepositoryKairos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/FakeMobileConnectionsRepositoryKairos.kt
new file mode 100644
index 000000000000..624b2cc12c89
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/FakeMobileConnectionsRepositoryKairos.kt
@@ -0,0 +1,134 @@
+/*
+ * 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.data.repository
+
+import android.telephony.SubscriptionManager.INVALID_SUBSCRIPTION_ID
+import android.telephony.TelephonyDisplayInfo
+import android.telephony.TelephonyManager
+import com.android.settingslib.SignalIcon
+import com.android.settingslib.mobile.MobileMappings
+import com.android.settingslib.mobile.TelephonyIcons
+import com.android.systemui.KairosBuilder
+import com.android.systemui.kairos.ExperimentalKairosApi
+import com.android.systemui.kairos.KairosNetwork
+import com.android.systemui.kairos.MutableEvents
+import com.android.systemui.kairos.MutableState
+import com.android.systemui.kairos.State
+import com.android.systemui.kairos.asIncremental
+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.kairosBuilder
+import com.android.systemui.log.table.TableLogBuffer
+import com.android.systemui.statusbar.pipeline.mobile.data.model.SubscriptionModel
+import com.android.systemui.statusbar.pipeline.mobile.util.FakeMobileMappingsProxy
+import com.android.systemui.statusbar.pipeline.mobile.util.MobileMappingsProxy
+
+// TODO(b/261632894): remove this in favor of the real impl or DemoMobileConnectionsRepositoryKairos
+@ExperimentalKairosApi
+class FakeMobileConnectionsRepositoryKairos(
+ kairos: KairosNetwork,
+ val tableLogBuffer: TableLogBuffer,
+ mobileMappings: MobileMappingsProxy = FakeMobileMappingsProxy(),
+) : MobileConnectionsRepositoryKairos, KairosBuilder by kairosBuilder() {
+
+ val GSM_KEY = mobileMappings.toIconKey(GSM)
+ val LTE_KEY = mobileMappings.toIconKey(LTE)
+ val UMTS_KEY = mobileMappings.toIconKey(UMTS)
+ val LTE_ADVANCED_KEY = mobileMappings.toIconKeyOverride(LTE_ADVANCED_PRO)
+
+ /**
+ * To avoid a reliance on [MobileMappings], we'll build a simpler map from network type to
+ * mobile icon. See TelephonyManager.NETWORK_TYPES for a list of types and [TelephonyIcons] for
+ * the exhaustive set of icons
+ */
+ val TEST_MAPPING: Map<String, SignalIcon.MobileIconGroup> =
+ mapOf(
+ GSM_KEY to TelephonyIcons.THREE_G,
+ LTE_KEY to TelephonyIcons.LTE,
+ UMTS_KEY to TelephonyIcons.FOUR_G,
+ LTE_ADVANCED_KEY to TelephonyIcons.NR_5G,
+ )
+
+ override val subscriptions = MutableState(kairos, emptyList<SubscriptionModel>())
+
+ override val mobileConnectionsBySubId = buildIncremental {
+ subscriptions
+ .map { it.associate { sub -> sub.subscriptionId to Unit } }
+ .asIncremental()
+ .mapValues { (subId, _) ->
+ buildSpec {
+ FakeMobileConnectionRepositoryKairos(subId, kairosNetwork, tableLogBuffer)
+ }
+ }
+ .applyLatestSpecForKey()
+ }
+
+ private val _activeMobileDataSubscriptionId = MutableState<Int?>(kairos, null)
+ override val activeMobileDataSubscriptionId: State<Int?> = _activeMobileDataSubscriptionId
+
+ override val activeMobileDataRepository: State<MobileConnectionRepositoryKairos?> =
+ combine(mobileConnectionsBySubId, activeMobileDataSubscriptionId) { conns, activeSub ->
+ conns[activeSub]
+ }
+
+ override val activeSubChangedInGroupEvent = MutableEvents<Unit>(kairos)
+
+ override val defaultDataSubId = MutableState(kairos, INVALID_SUBSCRIPTION_ID)
+
+ override val mobileIsDefault = MutableState(kairos, false)
+
+ override val hasCarrierMergedConnection = MutableState(kairos, false)
+
+ override val defaultConnectionIsValidated = MutableState(kairos, false)
+
+ override val defaultDataSubRatConfig = MutableState(kairos, MobileMappings.Config())
+
+ override val defaultMobileIconMapping = MutableState(kairos, TEST_MAPPING)
+
+ override val defaultMobileIconGroup = MutableState(kairos, DEFAULT_ICON)
+
+ override val isDeviceEmergencyCallCapable = MutableState(kairos, false)
+
+ override val isAnySimSecure = MutableState(kairos, false)
+
+ override val isInEcmMode: State<Boolean> = MutableState(kairos, false)
+
+ fun setActiveMobileDataSubscriptionId(subId: Int) {
+ // Simulate the filtering that the repo does
+ if (subId == INVALID_SUBSCRIPTION_ID) {
+ _activeMobileDataSubscriptionId.setValue(null)
+ } else {
+ _activeMobileDataSubscriptionId.setValue(subId)
+ }
+ }
+
+ companion object {
+ val DEFAULT_ICON = TelephonyIcons.G
+
+ // Use [MobileMappings] to define some simple definitions
+ const val GSM = TelephonyManager.NETWORK_TYPE_GSM
+ const val LTE = TelephonyManager.NETWORK_TYPE_LTE
+ const val UMTS = TelephonyManager.NETWORK_TYPE_UMTS
+ const val LTE_ADVANCED_PRO = TelephonyDisplayInfo.OVERRIDE_NETWORK_TYPE_LTE_ADVANCED_PRO
+ }
+}
+
+@ExperimentalKairosApi
+val MobileConnectionsRepositoryKairos.fake
+ get() = this as FakeMobileConnectionsRepositoryKairos
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileDataRepositoryKairosKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileDataRepositoryKairosKosmos.kt
new file mode 100644
index 000000000000..f57cf99c1309
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileDataRepositoryKairosKosmos.kt
@@ -0,0 +1,149 @@
+/*
+ * 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.data.repository
+
+import android.content.applicationContext
+import android.telephony.SubscriptionManager
+import android.telephony.telephonyManager
+import com.android.keyguard.keyguardUpdateMonitor
+import com.android.systemui.broadcast.broadcastDispatcher
+import com.android.systemui.demoModeController
+import com.android.systemui.dump.dumpManager
+import com.android.systemui.kairos.ActivatedKairosFixture
+import com.android.systemui.kairos.ExperimentalKairosApi
+import com.android.systemui.kairos.KairosNetwork
+import com.android.systemui.kairos.MutableEvents
+import com.android.systemui.kairos.buildSpec
+import com.android.systemui.kairos.kairos
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.Kosmos.Fixture
+import com.android.systemui.kosmos.testDispatcher
+import com.android.systemui.log.table.TableLogBuffer
+import com.android.systemui.log.table.logcatTableLogBuffer
+import com.android.systemui.log.table.tableLogBufferFactory
+import com.android.systemui.statusbar.pipeline.airplane.data.repository.airplaneModeRepository
+import com.android.systemui.statusbar.pipeline.mobile.data.MobileInputLogger
+import com.android.systemui.statusbar.pipeline.mobile.data.repository.demo.DemoMobileConnectionsRepositoryKairos
+import com.android.systemui.statusbar.pipeline.mobile.data.repository.demo.DemoModeMobileConnectionDataSourceKairos
+import com.android.systemui.statusbar.pipeline.mobile.data.repository.demo.model.FakeNetworkEventModel
+import com.android.systemui.statusbar.pipeline.mobile.data.repository.prod.MobileConnectionsRepositoryKairosImpl
+import com.android.systemui.statusbar.pipeline.mobile.util.FakeSubscriptionManagerProxy
+import com.android.systemui.statusbar.pipeline.mobile.util.SubscriptionManagerProxy
+import com.android.systemui.statusbar.pipeline.shared.data.repository.connectivityRepository
+import com.android.systemui.statusbar.pipeline.wifi.data.repository.demo.DemoModeWifiDataSource
+import com.android.systemui.statusbar.pipeline.wifi.data.repository.wifiRepository
+import com.android.systemui.util.mockito.mockFixture
+import org.mockito.kotlin.mock
+
+@ExperimentalKairosApi
+var Kosmos.mobileConnectionsRepositoryKairos: MobileConnectionsRepositoryKairos by Fixture {
+ mobileRepositorySwitcherKairos
+}
+
+@ExperimentalKairosApi
+val Kosmos.fakeMobileConnectionsRepositoryKairos by ActivatedKairosFixture {
+ FakeMobileConnectionsRepositoryKairos(kairos, logcatTableLogBuffer(this), mobileMappingsProxy)
+}
+
+@ExperimentalKairosApi
+val Kosmos.demoMobileConnectionsRepositoryKairos by ActivatedKairosFixture {
+ DemoMobileConnectionsRepositoryKairos(
+ mobileDataSource = demoModeMobileConnectionDataSourceKairos,
+ wifiDataSource = wifiDataSource,
+ context = applicationContext,
+ logFactory = tableLogBufferFactory,
+ )
+}
+
+@ExperimentalKairosApi
+val Kosmos.demoModeMobileConnectionDataSourceKairos:
+ DemoModeMobileConnectionDataSourceKairos by Fixture {
+ FakeDemoModeMobileConnectionDataSourceKairos(kairos)
+}
+
+val Kosmos.wifiDataSource: DemoModeWifiDataSource by mockFixture()
+
+@ExperimentalKairosApi
+class FakeDemoModeMobileConnectionDataSourceKairos(kairos: KairosNetwork) :
+ DemoModeMobileConnectionDataSourceKairos {
+ override val mobileEvents = MutableEvents<FakeNetworkEventModel?>(kairos)
+}
+
+@ExperimentalKairosApi
+val DemoModeMobileConnectionDataSourceKairos.fake
+ get() = this as FakeDemoModeMobileConnectionDataSourceKairos
+
+@ExperimentalKairosApi
+val Kosmos.mobileRepositorySwitcherKairos:
+ MobileRepositorySwitcherKairos by ActivatedKairosFixture {
+ MobileRepositorySwitcherKairos(
+ realRepository = mobileConnectionsRepositoryKairosImpl,
+ demoRepositoryFactory = demoMobileConnectionsRepositoryKairosFactory,
+ demoModeController = demoModeController,
+ )
+}
+
+@ExperimentalKairosApi
+val Kosmos.demoMobileConnectionsRepositoryKairosFactory:
+ DemoMobileConnectionsRepositoryKairos.Factory by Fixture {
+ DemoMobileConnectionsRepositoryKairos.Factory {
+ DemoMobileConnectionsRepositoryKairos(
+ mobileDataSource = demoModeMobileConnectionDataSourceKairos,
+ wifiDataSource = wifiDataSource,
+ context = applicationContext,
+ logFactory = tableLogBufferFactory,
+ )
+ }
+}
+
+@ExperimentalKairosApi
+val Kosmos.mobileConnectionsRepositoryKairosImpl:
+ MobileConnectionsRepositoryKairosImpl by ActivatedKairosFixture {
+ MobileConnectionsRepositoryKairosImpl(
+ connectivityRepository = connectivityRepository,
+ subscriptionManager = subscriptionManager,
+ subscriptionManagerProxy = subscriptionManagerProxy,
+ telephonyManager = telephonyManager,
+ logger = mobileInputLogger,
+ tableLogger = summaryLogger,
+ mobileMappingsProxy = mobileMappingsProxy,
+ broadcastDispatcher = broadcastDispatcher,
+ context = applicationContext,
+ bgDispatcher = testDispatcher,
+ mainDispatcher = testDispatcher,
+ airplaneModeRepository = airplaneModeRepository,
+ wifiRepository = wifiRepository,
+ keyguardUpdateMonitor = keyguardUpdateMonitor,
+ dumpManager = dumpManager,
+ mobileRepoFactory = { mobileConnectionRepositoryKairosFactory },
+ )
+}
+
+val Kosmos.subscriptionManager: SubscriptionManager by mockFixture()
+val Kosmos.mobileInputLogger: MobileInputLogger by mockFixture()
+val Kosmos.summaryLogger: TableLogBuffer by Fixture { logcatTableLogBuffer(this, "summaryLogger") }
+
+@ExperimentalKairosApi
+val Kosmos.mobileConnectionRepositoryKairosFactory by Fixture {
+ MobileConnectionsRepositoryKairosImpl.ConnectionRepoFactory { subId ->
+ buildSpec { FakeMobileConnectionRepositoryKairos(subId, kairos, mock()) }
+ }
+}
+
+val Kosmos.subscriptionManagerProxy: SubscriptionManagerProxy by Fixture {
+ FakeSubscriptionManagerProxy()
+}
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/multivalentTests/src/com/android/systemui/statusbar/pipeline/mobile/util/FakeSubscriptionManagerProxy.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/pipeline/mobile/util/FakeSubscriptionManagerProxy.kt
index a80238167b85..a391c44018f5 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/mobile/util/FakeSubscriptionManagerProxy.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/pipeline/mobile/util/FakeSubscriptionManagerProxy.kt
@@ -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.
@@ -23,7 +23,7 @@ import android.telephony.SubscriptionManager.INVALID_SUBSCRIPTION_ID
class FakeSubscriptionManagerProxy(
/** Set the default data subId to be returned in [getDefaultDataSubscriptionId] */
var defaultDataSubId: Int = INVALID_SUBSCRIPTION_ID,
- var activeSubscriptionInfo: SubscriptionInfo? = null
+ var activeSubscriptionInfo: SubscriptionInfo? = null,
) : SubscriptionManagerProxy {
override fun getDefaultDataSubscriptionId(): Int = defaultDataSubId
@@ -41,3 +41,6 @@ class FakeSubscriptionManagerProxy(
SubscriptionInfo.Builder().setId(subId).setEmbedded(isEmbedded).build()
}
}
+
+val SubscriptionManagerProxy.fake
+ get() = this as FakeSubscriptionManagerProxy
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/pipeline/shared/data/repository/ConnectivityRepositoryKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/pipeline/shared/data/repository/ConnectivityRepositoryKosmos.kt
index 00bfa994aabd..bb254a18efb7 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/pipeline/shared/data/repository/ConnectivityRepositoryKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/pipeline/shared/data/repository/ConnectivityRepositoryKosmos.kt
@@ -18,5 +18,5 @@ package com.android.systemui.statusbar.pipeline.shared.data.repository
import com.android.systemui.kosmos.Kosmos
-val Kosmos.connectivityRepository: ConnectivityRepository by
+var Kosmos.connectivityRepository: ConnectivityRepository by
Kosmos.Fixture { FakeConnectivityRepository() }
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/statusbar/pipeline/wifi/data/repository/WifiRepositoryKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/WifiRepositoryKosmos.kt
index e44061a718d5..f560c502d606 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/WifiRepositoryKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/WifiRepositoryKosmos.kt
@@ -19,4 +19,4 @@ package com.android.systemui.statusbar.pipeline.wifi.data.repository
import com.android.systemui.kosmos.Kosmos
val Kosmos.fakeWifiRepository: FakeWifiRepository by Kosmos.Fixture { FakeWifiRepository() }
-val Kosmos.wifiRepository: WifiRepository by Kosmos.Fixture { fakeWifiRepository }
+var Kosmos.wifiRepository: WifiRepository by Kosmos.Fixture { fakeWifiRepository }
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/src/com/android/systemui/media/controls/ui/viewmodel/MediaRecViewModel.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/util/CarrierConfigTrackerKosmos.kt
index 77add4035067..58473693954c 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/ui/viewmodel/MediaRecViewModel.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/util/CarrierConfigTrackerKosmos.kt
@@ -14,20 +14,9 @@
* limitations under the License.
*/
-package com.android.systemui.media.controls.ui.viewmodel
+package com.android.systemui.util
-import android.graphics.drawable.Drawable
-import android.graphics.drawable.Icon
-import com.android.systemui.animation.Expandable
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.util.mockito.mockFixture
-/** 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),
-)
+var Kosmos.carrierConfigTracker: CarrierConfigTracker by mockFixture()
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/util/mockito/MockitoKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/util/mockito/MockitoKosmos.kt
new file mode 100644
index 000000000000..1638cb7772ac
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/util/mockito/MockitoKosmos.kt
@@ -0,0 +1,22 @@
+/*
+ * 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.util.mockito
+
+import com.android.systemui.kosmos.Kosmos.Fixture
+import org.mockito.kotlin.mock
+
+inline fun <reified T> mockFixture(): Fixture<T> = Fixture { mock() }
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/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/proto/src/metrics_constants/OWNERS b/proto/src/metrics_constants/OWNERS
index 7009282b66e1..169f887ede56 100644
--- a/proto/src/metrics_constants/OWNERS
+++ b/proto/src/metrics_constants/OWNERS
@@ -1,4 +1,2 @@
cwren@android.com
-yanglu@google.com
yaochen@google.com
-yro@google.com
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-helper-src/libcore-fake/libcore/util/NonNull.java b/ravenwood/runtime-helper-src/libcore-fake/libcore/util/NonNull.java
index db3cd8ed712f..1153a77d5c9a 100644
--- a/ravenwood/runtime-helper-src/libcore-fake/libcore/util/NonNull.java
+++ b/ravenwood/runtime-helper-src/libcore-fake/libcore/util/NonNull.java
@@ -35,14 +35,4 @@ import java.lang.annotation.Target;
@Retention(SOURCE)
@Target({FIELD, METHOD, PARAMETER, TYPE_USE})
@libcore.api.IntraCoreApi
-public @interface NonNull {
- /**
- * Min Android API level (inclusive) to which this annotation is applied.
- */
- int from() default Integer.MIN_VALUE;
-
- /**
- * Max Android API level to which this annotation is applied.
- */
- int to() default Integer.MAX_VALUE;
-}
+public @interface NonNull {}
diff --git a/ravenwood/runtime-helper-src/libcore-fake/libcore/util/Nullable.java b/ravenwood/runtime-helper-src/libcore-fake/libcore/util/Nullable.java
index 3371978b0568..295f083426ff 100644
--- a/ravenwood/runtime-helper-src/libcore-fake/libcore/util/Nullable.java
+++ b/ravenwood/runtime-helper-src/libcore-fake/libcore/util/Nullable.java
@@ -35,14 +35,4 @@ import java.lang.annotation.Target;
@Retention(SOURCE)
@Target({FIELD, METHOD, PARAMETER, TYPE_USE})
@libcore.api.IntraCoreApi
-public @interface Nullable {
- /**
- * Min Android API level (inclusive) to which this annotation is applied.
- */
- int from() default Integer.MIN_VALUE;
-
- /**
- * Max Android API level to which this annotation is applied.
- */
- int to() default Integer.MAX_VALUE;
-}
+public @interface Nullable {}
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/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..f59e143c1e4e 100644
--- a/ravenwood/tools/hoststubgen/src/com/android/hoststubgen/Exceptions.kt
+++ b/ravenwood/tools/hoststubgen/lib/com/android/hoststubgen/Exceptions.kt
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..4fe21eac6972
--- /dev/null
+++ b/ravenwood/tools/hoststubgen/lib/com/android/hoststubgen/HostStubGenClassProcessor.kt
@@ -0,0 +1,165 @@
+/*
+ * 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.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.utils.ClassPredicate
+import com.android.hoststubgen.visitors.BaseAdapter
+import com.android.hoststubgen.visitors.PackageRedirectRemapper
+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
+
+/**
+ * This class implements bytecode transformation of HostStubGen.
+ */
+class HostStubGenClassProcessor(
+ private val options: HostStubGenClassProcessorOptions,
+ private 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)
+
+ /**
+ * 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
+ }
+
+ fun processClassBytecode(bytecode: ByteArray): ByteArray {
+ val cr = ClassReader(bytecode)
+
+ // 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 (options.enableClassChecker.get) {
+ outVisitor = CheckClassAdapter(outVisitor)
+ }
+
+ // Remapping should happen at the end.
+ 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(
+ cr.className, allClasses, outVisitor, filter,
+ packageRedirector, visitorOptions
+ )
+
+ 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..c7c45e65a8b6
--- /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.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<String> = 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, ai: ArgIterator): Boolean {
+ // Define some shorthands...
+ fun nextArg(): String = ai.nextArgRequired(option)
+ fun MutableSet<String>.addUniqueAnnotationArg(): String =
+ nextArg().also { this += ensureUniqueAnnotation(it) }
+
+ when (option) {
+ "--policy-override-file" ->
+ policyOverrideFiles.add(nextArg().ensureFileExists())
+
+ "--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 735635cc309a..b41ce0f65017 100644
--- a/ravenwood/tools/hoststubgen/src/com/android/hoststubgen/asm/AsmUtils.kt
+++ b/ravenwood/tools/hoststubgen/lib/com/android/hoststubgen/asm/AsmUtils.kt
@@ -268,18 +268,45 @@ fun writeByteCodeToReturn(methodDescriptor: String, writer: MethodVisitor) {
}
/**
+ * Write bytecode to pop the 2 uninitialized instances out of the stack
+ * after performing constructor redirection.
+ */
+fun adjustStackForConstructorRedirection(writer: MethodVisitor) {
+ // Stack: { uninitialized, uninitialized, obj }
+ writer.visitInsn(Opcodes.SWAP)
+ // Stack: { uninitialized, obj, uninitialized }
+ writer.visitInsn(Opcodes.POP)
+ // Stack: { uninitialized, obj }
+ writer.visitInsn(Opcodes.SWAP)
+ // Stack: { obj, uninitialized }
+ writer.visitInsn(Opcodes.POP)
+ // Stack: { obj }
+
+ // We end up with only the desired object on the stack
+}
+
+/**
* Given a method descriptor, insert an [argType] as the first argument to it.
*/
fun prependArgTypeToMethodDescriptor(methodDescriptor: String, classInternalName: String): String {
val returnType = Type.getReturnType(methodDescriptor)
val argTypes = Type.getArgumentTypes(methodDescriptor).toMutableList()
- argTypes.add(0, Type.getType("L" + classInternalName + ";"))
+ argTypes.add(0, Type.getType("L$classInternalName;"))
return Type.getMethodDescriptor(returnType, *argTypes.toTypedArray())
}
/**
+ * Given a method descriptor, change the return type to [classInternalName].
+ */
+fun changeMethodDescriptorReturnType(methodDescriptor: String, classInternalName: String): String {
+ val argTypes = Type.getArgumentTypes(methodDescriptor)
+ val returnType = Type.getType("L$classInternalName;")
+ return Type.getMethodDescriptor(returnType, *argTypes)
+}
+
+/**
* Return the "visibility" modifier from an `access` integer.
*
* (see https://docs.oracle.com/javase/specs/jvms/se9/html/jvms-4.html#jvms-4.1-200-E.1)
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..b8b0d8a31268 100644
--- a/ravenwood/tools/hoststubgen/src/com/android/hoststubgen/filters/DelegatingFilter.kt
+++ b/ravenwood/tools/hoststubgen/lib/com/android/hoststubgen/filters/DelegatingFilter.kt
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..474da6dfa1b9 100644
--- a/ravenwood/tools/hoststubgen/src/com/android/hoststubgen/filters/ImplicitOutputFilter.kt
+++ b/ravenwood/tools/hoststubgen/lib/com/android/hoststubgen/filters/ImplicitOutputFilter.kt
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..fc885d6f463b 100644
--- a/ravenwood/tools/hoststubgen/src/com/android/hoststubgen/filters/InMemoryOutputFilter.kt
+++ b/ravenwood/tools/hoststubgen/lib/com/android/hoststubgen/filters/InMemoryOutputFilter.kt
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..f99ce906240a 100644
--- a/ravenwood/tools/hoststubgen/src/com/android/hoststubgen/filters/OutputFilter.kt
+++ b/ravenwood/tools/hoststubgen/lib/com/android/hoststubgen/filters/OutputFilter.kt
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 d0c97c006647..dd353e9caeff 100644
--- a/ravenwood/tools/hoststubgen/src/com/android/hoststubgen/filters/TextFileFilterPolicyParser.kt
+++ b/ravenwood/tools/hoststubgen/lib/com/android/hoststubgen/filters/TextFileFilterPolicyParser.kt
@@ -100,7 +100,6 @@ interface PolicyFileProcessor {
methodName: String,
methodDesc: String,
replaceSpec: TextFilePolicyMethodReplaceFilter.MethodCallReplaceSpec,
- policy: FilterPolicyWithReason,
)
}
@@ -286,9 +285,10 @@ class TextFileFilterPolicyBuilder(
methodName: String,
methodDesc: String,
replaceSpec: TextFilePolicyMethodReplaceFilter.MethodCallReplaceSpec,
- policy: FilterPolicyWithReason,
) {
- imf.setPolicyForMethod(className, methodName, methodDesc, policy)
+ // Keep the source method, because the target method may call it.
+ imf.setPolicyForMethod(className, methodName, methodDesc,
+ FilterPolicy.Keep.withReason(FILTER_REASON))
methodReplaceSpec.add(replaceSpec)
}
}
@@ -642,7 +642,6 @@ class TextFileFilterPolicyParser {
methodName,
signature,
spec,
- policyWithReason,
)
} else {
// It's an in-class replace.
diff --git a/ravenwood/tools/hoststubgen/src/com/android/hoststubgen/filters/TextFilePolicyMethodReplaceFilter.kt b/ravenwood/tools/hoststubgen/lib/com/android/hoststubgen/filters/TextFilePolicyMethodReplaceFilter.kt
index a3f934cacc2c..a3f934cacc2c 100644
--- a/ravenwood/tools/hoststubgen/src/com/android/hoststubgen/filters/TextFilePolicyMethodReplaceFilter.kt
+++ b/ravenwood/tools/hoststubgen/lib/com/android/hoststubgen/filters/TextFilePolicyMethodReplaceFilter.kt
diff --git a/ravenwood/tools/hoststubgen/src/com/android/hoststubgen/filters/TextFilePolicyRemapperFilter.kt b/ravenwood/tools/hoststubgen/lib/com/android/hoststubgen/filters/TextFilePolicyRemapperFilter.kt
index bc90d1248322..bc90d1248322 100644
--- a/ravenwood/tools/hoststubgen/src/com/android/hoststubgen/filters/TextFilePolicyRemapperFilter.kt
+++ b/ravenwood/tools/hoststubgen/lib/com/android/hoststubgen/filters/TextFilePolicyRemapperFilter.kt
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..0b17879b862c
--- /dev/null
+++ b/ravenwood/tools/hoststubgen/lib/com/android/hoststubgen/utils/OptionUtils.kt
@@ -0,0 +1,206 @@
+/*
+ * 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.ensureFileExists
+import com.android.hoststubgen.log
+import com.android.hoststubgen.normalizeTextLine
+import java.io.BufferedReader
+import java.io.FileReader
+
+/**
+ * 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, ai: 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 {
+ 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: List<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() ?: break // EOF
+
+ line = normalizeTextLine(line)
+ if (line.isNotEmpty()) {
+ ret += line
+ }
+ }
+ }
+ }
+ return ret
+ }
+ }
+}
+
+/**
+ * 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")
+ }
+ }
+}
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..a08d1d605949 100644
--- a/ravenwood/tools/hoststubgen/src/com/android/hoststubgen/visitors/BaseAdapter.kt
+++ b/ravenwood/tools/hoststubgen/lib/com/android/hoststubgen/visitors/BaseAdapter.kt
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 70e7d46bb6cd..b8a357668c2b 100644
--- a/ravenwood/tools/hoststubgen/src/com/android/hoststubgen/visitors/ImplGeneratingAdapter.kt
+++ b/ravenwood/tools/hoststubgen/lib/com/android/hoststubgen/visitors/ImplGeneratingAdapter.kt
@@ -17,7 +17,10 @@ package com.android.hoststubgen.visitors
import com.android.hoststubgen.asm.CLASS_INITIALIZER_DESC
import com.android.hoststubgen.asm.CLASS_INITIALIZER_NAME
+import com.android.hoststubgen.asm.CTOR_NAME
import com.android.hoststubgen.asm.ClassNodes
+import com.android.hoststubgen.asm.adjustStackForConstructorRedirection
+import com.android.hoststubgen.asm.changeMethodDescriptorReturnType
import com.android.hoststubgen.asm.prependArgTypeToMethodDescriptor
import com.android.hoststubgen.asm.writeByteCodeToPushArguments
import com.android.hoststubgen.asm.writeByteCodeToReturn
@@ -33,6 +36,7 @@ import org.objectweb.asm.ClassVisitor
import org.objectweb.asm.MethodVisitor
import org.objectweb.asm.Opcodes
import org.objectweb.asm.Opcodes.INVOKEINTERFACE
+import org.objectweb.asm.Opcodes.INVOKESPECIAL
import org.objectweb.asm.Opcodes.INVOKESTATIC
import org.objectweb.asm.Opcodes.INVOKEVIRTUAL
import org.objectweb.asm.Type
@@ -376,53 +380,90 @@ class ImplGeneratingAdapter(
val callerMethodName: String,
next: MethodVisitor?,
) : MethodVisitor(OPCODE_VERSION, next) {
- override fun visitMethodInsn(
+
+ private fun doReplace(
opcode: Int,
- owner: String?,
- name: String?,
- descriptor: String?,
- isInterface: Boolean,
- ) {
+ owner: String,
+ name: String,
+ descriptor: String,
+ ): Boolean {
when (opcode) {
INVOKESTATIC, INVOKEVIRTUAL, INVOKEINTERFACE -> {}
- else -> {
- // Don't touch other opcodes.
- super.visitMethodInsn(opcode, owner, name, descriptor, isInterface)
- return
- }
+ // We only support INVOKESPECIAL when replacing constructors.
+ INVOKESPECIAL -> if (name != CTOR_NAME) return false
+ // Don't touch other opcodes.
+ else -> return false
}
+
val to = filter.getMethodCallReplaceTo(
- currentClassName, callerMethodName, owner!!, name!!, descriptor!!
+ currentClassName, callerMethodName, owner, name, descriptor
)
if (to == null
// Don't replace if the target is the callsite.
|| (to.className == currentClassName && to.methodName == callerMethodName)
) {
- super.visitMethodInsn(opcode, owner, name, descriptor, isInterface)
- return
+ return false
}
- // Replace the method call with a (static) call to the target method.
- // If it's a non-static call, the target method's first argument will receive "this".
- // (Because of that, we don't need to manipulate the stack. Just replace the
- // method call.)
+ if (opcode != INVOKESPECIAL) {
+ // It's either a static method call or virtual method call.
+ // Either way, we don't manipulate the stack and send the original arguments
+ // as is to the target method.
+ //
+ // If the call is a virtual call (INVOKEVIRTUAL or INVOKEINTERFACE), then
+ // the first argument in the stack is the "this" object, so the target
+ // method must have an extra argument as the first argument to receive it.
+ // We update the method descriptor with prependArgTypeToMethodDescriptor()
+ // to absorb this difference.
+
+ val toDesc = if (opcode == INVOKESTATIC) {
+ descriptor
+ } else {
+ prependArgTypeToMethodDescriptor(descriptor, owner)
+ }
- val toDesc = if (opcode == INVOKESTATIC) {
- // Static call to static call, no need to change the desc.
- descriptor
+ mv.visitMethodInsn(
+ INVOKESTATIC,
+ to.className,
+ to.methodName,
+ toDesc,
+ false
+ )
} else {
- // Need to prepend the "this" type to the descriptor.
- prependArgTypeToMethodDescriptor(descriptor, owner)
+ // Because an object initializer does not return a value, the newly created
+ // but uninitialized object will be dup-ed at the bottom of the stack.
+ // We first call the target method to consume the constructor arguments at the top.
+
+ val toDesc = changeMethodDescriptorReturnType(descriptor, owner)
+
+ // Before stack: { uninitialized, uninitialized, args... }
+ mv.visitMethodInsn(
+ INVOKESTATIC,
+ to.className,
+ to.methodName,
+ toDesc,
+ false
+ )
+ // After stack: { uninitialized, uninitialized, obj }
+
+ // Next we pop the 2 uninitialized instances out of the stack.
+ adjustStackForConstructorRedirection(mv)
}
- mv.visitMethodInsn(
- INVOKESTATIC,
- to.className,
- to.methodName,
- toDesc,
- false
- )
+ return true
+ }
+
+ override fun visitMethodInsn(
+ opcode: Int,
+ owner: String,
+ name: String,
+ descriptor: String,
+ isInterface: Boolean,
+ ) {
+ if (!doReplace(opcode, owner, name, descriptor)) {
+ super.visitMethodInsn(opcode, owner, name, descriptor, isInterface)
+ }
}
}
}
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..8bb454fa12e7 100644
--- a/ravenwood/tools/hoststubgen/src/com/android/hoststubgen/HostStubGenOptions.kt
+++ b/ravenwood/tools/hoststubgen/src/com/android/hoststubgen/HostStubGenOptions.kt
@@ -15,385 +15,102 @@
*/
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
/**
* 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, ai: ArgIterator): Boolean {
+ // Define some shorthands...
+ fun nextArg(): String = ai.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, ai)
}
- 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/test-tiny-framework/golden-output.RELEASE_TARGET_JAVA_21/01-hoststubgen-test-tiny-framework-orig-dump.txt b/ravenwood/tools/hoststubgen/test-tiny-framework/golden-output.RELEASE_TARGET_JAVA_21/01-hoststubgen-test-tiny-framework-orig-dump.txt
index e846d6e19ed9..2b942a91a8f8 100644
--- a/ravenwood/tools/hoststubgen/test-tiny-framework/golden-output.RELEASE_TARGET_JAVA_21/01-hoststubgen-test-tiny-framework-orig-dump.txt
+++ b/ravenwood/tools/hoststubgen/test-tiny-framework/golden-output.RELEASE_TARGET_JAVA_21/01-hoststubgen-test-tiny-framework-orig-dump.txt
@@ -1883,6 +1883,42 @@ BootstrapMethods:
InnerClasses:
public static #x= #x of #x; // Nested=class com/android/hoststubgen/test/tinyframework/TinyFrameworkLambdas$Nested of class com/android/hoststubgen/test/tinyframework/TinyFrameworkLambdas
public static final #x= #x of #x; // Lookup=class java/lang/invoke/MethodHandles$Lookup of class java/lang/invoke/MethodHandles
+## Class: com/android/hoststubgen/test/tinyframework/TinyFrameworkMethodCallReplace$ConstructorTester.class
+ Compiled from "TinyFrameworkMethodCallReplace.java"
+public class com.android.hoststubgen.test.tinyframework.TinyFrameworkMethodCallReplace$ConstructorTester
+ minor version: 0
+ major version: 65
+ flags: (0x0021) ACC_PUBLIC, ACC_SUPER
+ this_class: #x // com/android/hoststubgen/test/tinyframework/TinyFrameworkMethodCallReplace$ConstructorTester
+ super_class: #x // java/lang/Object
+ interfaces: 0, fields: 1, methods: 1, attributes: 3
+Constant pool:
+{
+ public int i;
+ descriptor: I
+ flags: (0x0001) ACC_PUBLIC
+
+ public com.android.hoststubgen.test.tinyframework.TinyFrameworkMethodCallReplace$ConstructorTester(int);
+ descriptor: (I)V
+ flags: (0x0001) ACC_PUBLIC
+ Code:
+ stack=2, locals=2, args_size=2
+ x: aload_0
+ x: invokespecial #x // Method java/lang/Object."<init>":()V
+ x: aload_0
+ x: iload_1
+ x: putfield #x // Field i:I
+ x: return
+ LineNumberTable:
+ LocalVariableTable:
+ Start Length Slot Name Signature
+ 0 10 0 this Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkMethodCallReplace$ConstructorTester;
+ 0 10 1 i I
+}
+SourceFile: "TinyFrameworkMethodCallReplace.java"
+NestHost: class com/android/hoststubgen/test/tinyframework/TinyFrameworkMethodCallReplace
+InnerClasses:
+ public static #x= #x of #x; // ConstructorTester=class com/android/hoststubgen/test/tinyframework/TinyFrameworkMethodCallReplace$ConstructorTester of class com/android/hoststubgen/test/tinyframework/TinyFrameworkMethodCallReplace
## Class: com/android/hoststubgen/test/tinyframework/TinyFrameworkMethodCallReplace$ReplaceTo.class
Compiled from "TinyFrameworkMethodCallReplace.java"
public class com.android.hoststubgen.test.tinyframework.TinyFrameworkMethodCallReplace$ReplaceTo
@@ -1891,7 +1927,7 @@ public class com.android.hoststubgen.test.tinyframework.TinyFrameworkMethodCallR
flags: (0x0021) ACC_PUBLIC, ACC_SUPER
this_class: #x // com/android/hoststubgen/test/tinyframework/TinyFrameworkMethodCallReplace$ReplaceTo
super_class: #x // java/lang/Object
- interfaces: 0, fields: 0, methods: 3, attributes: 3
+ interfaces: 0, fields: 0, methods: 4, attributes: 3
Constant pool:
{
public com.android.hoststubgen.test.tinyframework.TinyFrameworkMethodCallReplace$ReplaceTo();
@@ -1937,10 +1973,28 @@ Constant pool:
Start Length Slot Name Signature
0 4 0 a I
0 4 1 b I
+
+ public static com.android.hoststubgen.test.tinyframework.TinyFrameworkMethodCallReplace$ConstructorTester newConstructorTester(int);
+ descriptor: (I)Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkMethodCallReplace$ConstructorTester;
+ flags: (0x0009) ACC_PUBLIC, ACC_STATIC
+ Code:
+ stack=4, locals=1, args_size=1
+ x: new #x // class com/android/hoststubgen/test/tinyframework/TinyFrameworkMethodCallReplace$ConstructorTester
+ x: dup
+ x: iload_0
+ x: iconst_1
+ x: iadd
+ x: invokespecial #x // Method com/android/hoststubgen/test/tinyframework/TinyFrameworkMethodCallReplace$ConstructorTester."<init>":(I)V
+ x: areturn
+ LineNumberTable:
+ LocalVariableTable:
+ Start Length Slot Name Signature
+ 0 11 0 i I
}
SourceFile: "TinyFrameworkMethodCallReplace.java"
NestHost: class com/android/hoststubgen/test/tinyframework/TinyFrameworkMethodCallReplace
InnerClasses:
+ public static #x= #x of #x; // ConstructorTester=class com/android/hoststubgen/test/tinyframework/TinyFrameworkMethodCallReplace$ConstructorTester of class com/android/hoststubgen/test/tinyframework/TinyFrameworkMethodCallReplace
public static #x= #x of #x; // ReplaceTo=class com/android/hoststubgen/test/tinyframework/TinyFrameworkMethodCallReplace$ReplaceTo of class com/android/hoststubgen/test/tinyframework/TinyFrameworkMethodCallReplace
## Class: com/android/hoststubgen/test/tinyframework/TinyFrameworkMethodCallReplace.class
Compiled from "TinyFrameworkMethodCallReplace.java"
@@ -1950,7 +2004,7 @@ public class com.android.hoststubgen.test.tinyframework.TinyFrameworkMethodCallR
flags: (0x0021) ACC_PUBLIC, ACC_SUPER
this_class: #x // com/android/hoststubgen/test/tinyframework/TinyFrameworkMethodCallReplace
super_class: #x // java/lang/Object
- interfaces: 0, fields: 0, methods: 5, attributes: 5
+ interfaces: 0, fields: 0, methods: 6, attributes: 5
Constant pool:
{
public com.android.hoststubgen.test.tinyframework.TinyFrameworkMethodCallReplace();
@@ -2008,6 +2062,21 @@ Constant pool:
x: ireturn
LineNumberTable:
+ public static com.android.hoststubgen.test.tinyframework.TinyFrameworkMethodCallReplace$ConstructorTester constructorReplaceTester(int);
+ descriptor: (I)Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkMethodCallReplace$ConstructorTester;
+ flags: (0x0009) ACC_PUBLIC, ACC_STATIC
+ Code:
+ stack=3, locals=1, args_size=1
+ x: new #x // class com/android/hoststubgen/test/tinyframework/TinyFrameworkMethodCallReplace$ConstructorTester
+ x: dup
+ x: iload_0
+ x: invokespecial #x // Method com/android/hoststubgen/test/tinyframework/TinyFrameworkMethodCallReplace$ConstructorTester."<init>":(I)V
+ x: areturn
+ LineNumberTable:
+ LocalVariableTable:
+ Start Length Slot Name Signature
+ 0 9 0 i I
+
private static int originalAdd(int, int);
descriptor: (II)I
flags: (0x000a) ACC_PRIVATE, ACC_STATIC
@@ -2046,6 +2115,7 @@ RuntimeInvisibleAnnotations:
android.hosttest.annotation.HostSideTestWholeClassKeep
NestMembers:
com/android/hoststubgen/test/tinyframework/TinyFrameworkMethodCallReplace$ReplaceTo
+ com/android/hoststubgen/test/tinyframework/TinyFrameworkMethodCallReplace$ConstructorTester
BootstrapMethods:
x: #x REF_invokeStatic java/lang/invoke/LambdaMetafactory.metafactory:(Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/invoke/MethodType;Ljava/lang/invoke/MethodType;Ljava/lang/invoke/MethodHandle;Ljava/lang/invoke/MethodType;)Ljava/lang/invoke/CallSite;
Method arguments:
@@ -2053,8 +2123,9 @@ BootstrapMethods:
#x REF_invokeStatic com/android/hoststubgen/test/tinyframework/TinyFrameworkMethodCallReplace.lambda$nonStaticMethodCallReplaceTester$0:(Ljava/util/concurrent/atomic/AtomicBoolean;)V
#x ()V
InnerClasses:
+ public static #x= #x of #x; // ConstructorTester=class com/android/hoststubgen/test/tinyframework/TinyFrameworkMethodCallReplace$ConstructorTester of class com/android/hoststubgen/test/tinyframework/TinyFrameworkMethodCallReplace
public static #x= #x of #x; // ReplaceTo=class com/android/hoststubgen/test/tinyframework/TinyFrameworkMethodCallReplace$ReplaceTo of class com/android/hoststubgen/test/tinyframework/TinyFrameworkMethodCallReplace
- public static final #x= #x of #x; // Lookup=class java/lang/invoke/MethodHandles$Lookup of class java/lang/invoke/MethodHandles
+ public static final #x= #x of #x; // Lookup=class java/lang/invoke/MethodHandles$Lookup of class java/lang/invoke/MethodHandles
## Class: com/android/hoststubgen/test/tinyframework/TinyFrameworkNative.class
Compiled from "TinyFrameworkNative.java"
public class com.android.hoststubgen.test.tinyframework.TinyFrameworkNative
diff --git a/ravenwood/tools/hoststubgen/test-tiny-framework/golden-output.RELEASE_TARGET_JAVA_21/03-hoststubgen-test-tiny-framework-host-dump.txt b/ravenwood/tools/hoststubgen/test-tiny-framework/golden-output.RELEASE_TARGET_JAVA_21/03-hoststubgen-test-tiny-framework-host-dump.txt
index be95fe03d74f..d493ad152225 100644
--- a/ravenwood/tools/hoststubgen/test-tiny-framework/golden-output.RELEASE_TARGET_JAVA_21/03-hoststubgen-test-tiny-framework-host-dump.txt
+++ b/ravenwood/tools/hoststubgen/test-tiny-framework/golden-output.RELEASE_TARGET_JAVA_21/03-hoststubgen-test-tiny-framework-host-dump.txt
@@ -2002,6 +2002,51 @@ BootstrapMethods:
#x ()Ljava/lang/Integer;
NestMembers:
com/android/hoststubgen/test/tinyframework/TinyFrameworkLambdas$Nested
+## Class: com/android/hoststubgen/test/tinyframework/TinyFrameworkMethodCallReplace$ConstructorTester.class
+ Compiled from "TinyFrameworkMethodCallReplace.java"
+public class com.android.hoststubgen.test.tinyframework.TinyFrameworkMethodCallReplace$ConstructorTester
+ minor version: 0
+ major version: 65
+ flags: (0x0021) ACC_PUBLIC, ACC_SUPER
+ this_class: #x // com/android/hoststubgen/test/tinyframework/TinyFrameworkMethodCallReplace$ConstructorTester
+ super_class: #x // java/lang/Object
+ interfaces: 0, fields: 1, methods: 1, attributes: 4
+Constant pool:
+{
+ public int i;
+ descriptor: I
+ flags: (0x0001) ACC_PUBLIC
+ RuntimeVisibleAnnotations:
+ x: #x()
+ com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
+
+ public com.android.hoststubgen.test.tinyframework.TinyFrameworkMethodCallReplace$ConstructorTester(int);
+ descriptor: (I)V
+ flags: (0x0001) ACC_PUBLIC
+ Code:
+ stack=2, locals=2, args_size=2
+ x: aload_0
+ x: invokespecial #x // Method java/lang/Object."<init>":()V
+ x: aload_0
+ x: iload_1
+ x: putfield #x // Field i:I
+ x: return
+ LineNumberTable:
+ LocalVariableTable:
+ Start Length Slot Name Signature
+ 0 10 0 this Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkMethodCallReplace$ConstructorTester;
+ 0 10 1 i I
+ RuntimeVisibleAnnotations:
+ x: #x()
+ com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
+}
+InnerClasses:
+ public static #x= #x of #x; // ConstructorTester=class com/android/hoststubgen/test/tinyframework/TinyFrameworkMethodCallReplace$ConstructorTester of class com/android/hoststubgen/test/tinyframework/TinyFrameworkMethodCallReplace
+SourceFile: "TinyFrameworkMethodCallReplace.java"
+RuntimeVisibleAnnotations:
+ x: #x()
+ com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
+NestHost: class com/android/hoststubgen/test/tinyframework/TinyFrameworkMethodCallReplace
## Class: com/android/hoststubgen/test/tinyframework/TinyFrameworkMethodCallReplace$ReplaceTo.class
Compiled from "TinyFrameworkMethodCallReplace.java"
public class com.android.hoststubgen.test.tinyframework.TinyFrameworkMethodCallReplace$ReplaceTo
@@ -2010,7 +2055,7 @@ public class com.android.hoststubgen.test.tinyframework.TinyFrameworkMethodCallR
flags: (0x0021) ACC_PUBLIC, ACC_SUPER
this_class: #x // com/android/hoststubgen/test/tinyframework/TinyFrameworkMethodCallReplace$ReplaceTo
super_class: #x // java/lang/Object
- interfaces: 0, fields: 0, methods: 3, attributes: 4
+ interfaces: 0, fields: 0, methods: 4, attributes: 4
Constant pool:
{
public com.android.hoststubgen.test.tinyframework.TinyFrameworkMethodCallReplace$ReplaceTo();
@@ -2065,9 +2110,30 @@ Constant pool:
RuntimeVisibleAnnotations:
x: #x()
com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
+
+ public static com.android.hoststubgen.test.tinyframework.TinyFrameworkMethodCallReplace$ConstructorTester newConstructorTester(int);
+ descriptor: (I)Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkMethodCallReplace$ConstructorTester;
+ flags: (0x0009) ACC_PUBLIC, ACC_STATIC
+ Code:
+ stack=4, locals=1, args_size=1
+ x: new #x // class com/android/hoststubgen/test/tinyframework/TinyFrameworkMethodCallReplace$ConstructorTester
+ x: dup
+ x: iload_0
+ x: iconst_1
+ x: iadd
+ x: invokespecial #x // Method com/android/hoststubgen/test/tinyframework/TinyFrameworkMethodCallReplace$ConstructorTester."<init>":(I)V
+ x: areturn
+ LineNumberTable:
+ LocalVariableTable:
+ Start Length Slot Name Signature
+ 0 11 0 i I
+ RuntimeVisibleAnnotations:
+ x: #x()
+ com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
}
InnerClasses:
- public static #x= #x of #x; // ReplaceTo=class com/android/hoststubgen/test/tinyframework/TinyFrameworkMethodCallReplace$ReplaceTo of class com/android/hoststubgen/test/tinyframework/TinyFrameworkMethodCallReplace
+ public static #x= #x of #x; // ConstructorTester=class com/android/hoststubgen/test/tinyframework/TinyFrameworkMethodCallReplace$ConstructorTester of class com/android/hoststubgen/test/tinyframework/TinyFrameworkMethodCallReplace
+ public static #x= #x of #x; // ReplaceTo=class com/android/hoststubgen/test/tinyframework/TinyFrameworkMethodCallReplace$ReplaceTo of class com/android/hoststubgen/test/tinyframework/TinyFrameworkMethodCallReplace
SourceFile: "TinyFrameworkMethodCallReplace.java"
RuntimeVisibleAnnotations:
x: #x()
@@ -2081,7 +2147,7 @@ public class com.android.hoststubgen.test.tinyframework.TinyFrameworkMethodCallR
flags: (0x0021) ACC_PUBLIC, ACC_SUPER
this_class: #x // com/android/hoststubgen/test/tinyframework/TinyFrameworkMethodCallReplace
super_class: #x // java/lang/Object
- interfaces: 0, fields: 0, methods: 4, attributes: 6
+ interfaces: 0, fields: 0, methods: 6, attributes: 6
Constant pool:
{
public com.android.hoststubgen.test.tinyframework.TinyFrameworkMethodCallReplace();
@@ -2148,6 +2214,48 @@ Constant pool:
x: #x()
com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
+ public static com.android.hoststubgen.test.tinyframework.TinyFrameworkMethodCallReplace$ConstructorTester constructorReplaceTester(int);
+ descriptor: (I)Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkMethodCallReplace$ConstructorTester;
+ flags: (0x0009) ACC_PUBLIC, ACC_STATIC
+ Code:
+ stack=3, locals=1, args_size=1
+ x: new #x // class com/android/hoststubgen/test/tinyframework/TinyFrameworkMethodCallReplace$ConstructorTester
+ x: dup
+ x: iload_0
+ x: invokestatic #x // Method com/android/hoststubgen/test/tinyframework/TinyFrameworkMethodCallReplace$ReplaceTo.newConstructorTester:(I)Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkMethodCallReplace$ConstructorTester;
+ x: swap
+ x: pop
+ x: swap
+ x: pop
+ x: areturn
+ LineNumberTable:
+ LocalVariableTable:
+ Start Length Slot Name Signature
+ 0 13 0 i I
+ RuntimeVisibleAnnotations:
+ x: #x()
+ com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
+
+ private static int originalAdd(int, int);
+ descriptor: (II)I
+ flags: (0x000a) ACC_PRIVATE, ACC_STATIC
+ Code:
+ stack=2, locals=2, args_size=2
+ x: iload_0
+ x: iload_1
+ x: iadd
+ x: iconst_1
+ x: isub
+ x: ireturn
+ LineNumberTable:
+ LocalVariableTable:
+ Start Length Slot Name Signature
+ 0 6 0 a I
+ 0 6 1 b I
+ RuntimeVisibleAnnotations:
+ x: #x()
+ com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
+
private static void lambda$nonStaticMethodCallReplaceTester$0(java.util.concurrent.atomic.AtomicBoolean);
descriptor: (Ljava/util/concurrent/atomic/AtomicBoolean;)V
flags: (0x100a) ACC_PRIVATE, ACC_STATIC, ACC_SYNTHETIC
@@ -2167,6 +2275,7 @@ Constant pool:
com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
}
InnerClasses:
+ public static #x= #x of #x; // ConstructorTester=class com/android/hoststubgen/test/tinyframework/TinyFrameworkMethodCallReplace$ConstructorTester of class com/android/hoststubgen/test/tinyframework/TinyFrameworkMethodCallReplace
public static #x= #x of #x; // ReplaceTo=class com/android/hoststubgen/test/tinyframework/TinyFrameworkMethodCallReplace$ReplaceTo of class com/android/hoststubgen/test/tinyframework/TinyFrameworkMethodCallReplace
public static final #x= #x of #x; // Lookup=class java/lang/invoke/MethodHandles$Lookup of class java/lang/invoke/MethodHandles
SourceFile: "TinyFrameworkMethodCallReplace.java"
@@ -2184,6 +2293,7 @@ BootstrapMethods:
#x ()V
NestMembers:
com/android/hoststubgen/test/tinyframework/TinyFrameworkMethodCallReplace$ReplaceTo
+ com/android/hoststubgen/test/tinyframework/TinyFrameworkMethodCallReplace$ConstructorTester
## Class: com/android/hoststubgen/test/tinyframework/TinyFrameworkNative.class
Compiled from "TinyFrameworkNative.java"
public class com.android.hoststubgen.test.tinyframework.TinyFrameworkNative
diff --git a/ravenwood/tools/hoststubgen/test-tiny-framework/golden-output.RELEASE_TARGET_JAVA_21/13-hoststubgen-test-tiny-framework-host-ext-dump.txt b/ravenwood/tools/hoststubgen/test-tiny-framework/golden-output.RELEASE_TARGET_JAVA_21/13-hoststubgen-test-tiny-framework-host-ext-dump.txt
index 667981fcb7e8..8978a7acefd8 100644
--- a/ravenwood/tools/hoststubgen/test-tiny-framework/golden-output.RELEASE_TARGET_JAVA_21/13-hoststubgen-test-tiny-framework-host-ext-dump.txt
+++ b/ravenwood/tools/hoststubgen/test-tiny-framework/golden-output.RELEASE_TARGET_JAVA_21/13-hoststubgen-test-tiny-framework-host-ext-dump.txt
@@ -2413,6 +2413,66 @@ BootstrapMethods:
#x ()Ljava/lang/Integer;
NestMembers:
com/android/hoststubgen/test/tinyframework/TinyFrameworkLambdas$Nested
+## Class: com/android/hoststubgen/test/tinyframework/TinyFrameworkMethodCallReplace$ConstructorTester.class
+ Compiled from "TinyFrameworkMethodCallReplace.java"
+public class com.android.hoststubgen.test.tinyframework.TinyFrameworkMethodCallReplace$ConstructorTester
+ minor version: 0
+ major version: 65
+ flags: (0x0021) ACC_PUBLIC, ACC_SUPER
+ this_class: #x // com/android/hoststubgen/test/tinyframework/TinyFrameworkMethodCallReplace$ConstructorTester
+ super_class: #x // java/lang/Object
+ interfaces: 0, fields: 1, methods: 2, attributes: 4
+Constant pool:
+{
+ public int i;
+ descriptor: I
+ flags: (0x0001) ACC_PUBLIC
+ RuntimeVisibleAnnotations:
+ x: #x()
+ com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
+
+ private static {};
+ descriptor: ()V
+ flags: (0x000a) ACC_PRIVATE, ACC_STATIC
+ Code:
+ stack=2, locals=0, args_size=0
+ x: ldc #x // class com/android/hoststubgen/test/tinyframework/TinyFrameworkMethodCallReplace$ConstructorTester
+ x: ldc #x // String com.android.hoststubgen.hosthelper.HostTestUtils.logClassLoaded
+ x: invokestatic #x // Method com/android/hoststubgen/hosthelper/HostTestUtils.onClassLoaded:(Ljava/lang/Class;Ljava/lang/String;)V
+ x: return
+
+ public com.android.hoststubgen.test.tinyframework.TinyFrameworkMethodCallReplace$ConstructorTester(int);
+ descriptor: (I)V
+ flags: (0x0001) ACC_PUBLIC
+ Code:
+ stack=4, locals=2, args_size=2
+ x: ldc #x // class com/android/hoststubgen/test/tinyframework/TinyFrameworkMethodCallReplace$ConstructorTester
+ x: ldc #x // String <init>
+ x: ldc #x // String (I)V
+ x: ldc #x // String com.android.hoststubgen.hosthelper.HostTestUtils.logMethodCall
+ x: invokestatic #x // Method com/android/hoststubgen/hosthelper/HostTestUtils.callMethodCallHook:(Ljava/lang/Class;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V
+ x: aload_0
+ x: invokespecial #x // Method java/lang/Object."<init>":()V
+ x: aload_0
+ x: iload_1
+ x: putfield #x // Field i:I
+ x: return
+ LineNumberTable:
+ LocalVariableTable:
+ Start Length Slot Name Signature
+ 11 10 0 this Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkMethodCallReplace$ConstructorTester;
+ 11 10 1 i I
+ RuntimeVisibleAnnotations:
+ x: #x()
+ com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
+}
+InnerClasses:
+ public static #x= #x of #x; // ConstructorTester=class com/android/hoststubgen/test/tinyframework/TinyFrameworkMethodCallReplace$ConstructorTester of class com/android/hoststubgen/test/tinyframework/TinyFrameworkMethodCallReplace
+SourceFile: "TinyFrameworkMethodCallReplace.java"
+RuntimeVisibleAnnotations:
+ x: #x()
+ com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
+NestHost: class com/android/hoststubgen/test/tinyframework/TinyFrameworkMethodCallReplace
## Class: com/android/hoststubgen/test/tinyframework/TinyFrameworkMethodCallReplace$ReplaceTo.class
Compiled from "TinyFrameworkMethodCallReplace.java"
public class com.android.hoststubgen.test.tinyframework.TinyFrameworkMethodCallReplace$ReplaceTo
@@ -2421,7 +2481,7 @@ public class com.android.hoststubgen.test.tinyframework.TinyFrameworkMethodCallR
flags: (0x0021) ACC_PUBLIC, ACC_SUPER
this_class: #x // com/android/hoststubgen/test/tinyframework/TinyFrameworkMethodCallReplace$ReplaceTo
super_class: #x // java/lang/Object
- interfaces: 0, fields: 0, methods: 4, attributes: 4
+ interfaces: 0, fields: 0, methods: 5, attributes: 4
Constant pool:
{
private static {};
@@ -2501,8 +2561,34 @@ Constant pool:
RuntimeVisibleAnnotations:
x: #x()
com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
+
+ public static com.android.hoststubgen.test.tinyframework.TinyFrameworkMethodCallReplace$ConstructorTester newConstructorTester(int);
+ descriptor: (I)Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkMethodCallReplace$ConstructorTester;
+ flags: (0x0009) ACC_PUBLIC, ACC_STATIC
+ Code:
+ stack=4, locals=1, args_size=1
+ x: ldc #x // class com/android/hoststubgen/test/tinyframework/TinyFrameworkMethodCallReplace$ReplaceTo
+ x: ldc #x // String newConstructorTester
+ x: ldc #x // String (I)Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkMethodCallReplace$ConstructorTester;
+ x: ldc #x // String com.android.hoststubgen.hosthelper.HostTestUtils.logMethodCall
+ x: invokestatic #x // Method com/android/hoststubgen/hosthelper/HostTestUtils.callMethodCallHook:(Ljava/lang/Class;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V
+ x: new #x // class com/android/hoststubgen/test/tinyframework/TinyFrameworkMethodCallReplace$ConstructorTester
+ x: dup
+ x: iload_0
+ x: iconst_1
+ x: iadd
+ x: invokespecial #x // Method com/android/hoststubgen/test/tinyframework/TinyFrameworkMethodCallReplace$ConstructorTester."<init>":(I)V
+ x: areturn
+ LineNumberTable:
+ LocalVariableTable:
+ Start Length Slot Name Signature
+ 11 11 0 i I
+ RuntimeVisibleAnnotations:
+ x: #x()
+ com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
}
InnerClasses:
+ public static #x= #x of #x; // ConstructorTester=class com/android/hoststubgen/test/tinyframework/TinyFrameworkMethodCallReplace$ConstructorTester of class com/android/hoststubgen/test/tinyframework/TinyFrameworkMethodCallReplace
public static #x= #x of #x; // ReplaceTo=class com/android/hoststubgen/test/tinyframework/TinyFrameworkMethodCallReplace$ReplaceTo of class com/android/hoststubgen/test/tinyframework/TinyFrameworkMethodCallReplace
SourceFile: "TinyFrameworkMethodCallReplace.java"
RuntimeVisibleAnnotations:
@@ -2517,7 +2603,7 @@ public class com.android.hoststubgen.test.tinyframework.TinyFrameworkMethodCallR
flags: (0x0021) ACC_PUBLIC, ACC_SUPER
this_class: #x // com/android/hoststubgen/test/tinyframework/TinyFrameworkMethodCallReplace
super_class: #x // java/lang/Object
- interfaces: 0, fields: 0, methods: 5, attributes: 6
+ interfaces: 0, fields: 0, methods: 7, attributes: 6
Constant pool:
{
private static {};
@@ -2609,18 +2695,70 @@ Constant pool:
x: #x()
com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
+ public static com.android.hoststubgen.test.tinyframework.TinyFrameworkMethodCallReplace$ConstructorTester constructorReplaceTester(int);
+ descriptor: (I)Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkMethodCallReplace$ConstructorTester;
+ flags: (0x0009) ACC_PUBLIC, ACC_STATIC
+ Code:
+ stack=4, locals=1, args_size=1
+ x: ldc #x // class com/android/hoststubgen/test/tinyframework/TinyFrameworkMethodCallReplace
+ x: ldc #x // String constructorReplaceTester
+ x: ldc #x // String (I)Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkMethodCallReplace$ConstructorTester;
+ x: ldc #x // String com.android.hoststubgen.hosthelper.HostTestUtils.logMethodCall
+ x: invokestatic #x // Method com/android/hoststubgen/hosthelper/HostTestUtils.callMethodCallHook:(Ljava/lang/Class;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V
+ x: new #x // class com/android/hoststubgen/test/tinyframework/TinyFrameworkMethodCallReplace$ConstructorTester
+ x: dup
+ x: iload_0
+ x: invokestatic #x // Method com/android/hoststubgen/test/tinyframework/TinyFrameworkMethodCallReplace$ReplaceTo.newConstructorTester:(I)Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkMethodCallReplace$ConstructorTester;
+ x: swap
+ x: pop
+ x: swap
+ x: pop
+ x: areturn
+ LineNumberTable:
+ LocalVariableTable:
+ Start Length Slot Name Signature
+ 11 13 0 i I
+ RuntimeVisibleAnnotations:
+ x: #x()
+ com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
+
+ private static int originalAdd(int, int);
+ descriptor: (II)I
+ flags: (0x000a) ACC_PRIVATE, ACC_STATIC
+ Code:
+ stack=4, locals=2, args_size=2
+ x: ldc #x // class com/android/hoststubgen/test/tinyframework/TinyFrameworkMethodCallReplace
+ x: ldc #x // String originalAdd
+ x: ldc #x // String (II)I
+ x: ldc #x // String com.android.hoststubgen.hosthelper.HostTestUtils.logMethodCall
+ x: invokestatic #x // Method com/android/hoststubgen/hosthelper/HostTestUtils.callMethodCallHook:(Ljava/lang/Class;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V
+ x: iload_0
+ x: iload_1
+ x: iadd
+ x: iconst_1
+ x: isub
+ x: ireturn
+ LineNumberTable:
+ LocalVariableTable:
+ Start Length Slot Name Signature
+ 11 6 0 a I
+ 11 6 1 b I
+ RuntimeVisibleAnnotations:
+ x: #x()
+ com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
+
private static void lambda$nonStaticMethodCallReplaceTester$0(java.util.concurrent.atomic.AtomicBoolean);
descriptor: (Ljava/util/concurrent/atomic/AtomicBoolean;)V
flags: (0x100a) ACC_PRIVATE, ACC_STATIC, ACC_SYNTHETIC
Code:
stack=4, locals=1, args_size=1
x: ldc #x // class com/android/hoststubgen/test/tinyframework/TinyFrameworkMethodCallReplace
- x: ldc #x // String lambda$nonStaticMethodCallReplaceTester$0
- x: ldc #x // String (Ljava/util/concurrent/atomic/AtomicBoolean;)V
+ x: ldc #x // String lambda$nonStaticMethodCallReplaceTester$0
+ x: ldc #x // String (Ljava/util/concurrent/atomic/AtomicBoolean;)V
x: ldc #x // String com.android.hoststubgen.hosthelper.HostTestUtils.logMethodCall
x: invokestatic #x // Method com/android/hoststubgen/hosthelper/HostTestUtils.callMethodCallHook:(Ljava/lang/Class;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V
x: aload_0
- x: invokestatic #x // Method java/lang/Thread.currentThread:()Ljava/lang/Thread;
+ x: invokestatic #x // Method java/lang/Thread.currentThread:()Ljava/lang/Thread;
x: invokevirtual #x // Method java/lang/Thread.isDaemon:()Z
x: invokevirtual #x // Method java/util/concurrent/atomic/AtomicBoolean.set:(Z)V
x: return
@@ -2633,6 +2771,7 @@ Constant pool:
com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
}
InnerClasses:
+ public static #x= #x of #x; // ConstructorTester=class com/android/hoststubgen/test/tinyframework/TinyFrameworkMethodCallReplace$ConstructorTester of class com/android/hoststubgen/test/tinyframework/TinyFrameworkMethodCallReplace
public static #x= #x of #x; // ReplaceTo=class com/android/hoststubgen/test/tinyframework/TinyFrameworkMethodCallReplace$ReplaceTo of class com/android/hoststubgen/test/tinyframework/TinyFrameworkMethodCallReplace
public static final #x= #x of #x; // Lookup=class java/lang/invoke/MethodHandles$Lookup of class java/lang/invoke/MethodHandles
SourceFile: "TinyFrameworkMethodCallReplace.java"
@@ -2650,6 +2789,7 @@ BootstrapMethods:
#x ()V
NestMembers:
com/android/hoststubgen/test/tinyframework/TinyFrameworkMethodCallReplace$ReplaceTo
+ com/android/hoststubgen/test/tinyframework/TinyFrameworkMethodCallReplace$ConstructorTester
## Class: com/android/hoststubgen/test/tinyframework/TinyFrameworkNative.class
Compiled from "TinyFrameworkNative.java"
public class com.android.hoststubgen.test.tinyframework.TinyFrameworkNative
diff --git a/ravenwood/tools/hoststubgen/test-tiny-framework/golden-output/01-hoststubgen-test-tiny-framework-orig-dump.txt b/ravenwood/tools/hoststubgen/test-tiny-framework/golden-output/01-hoststubgen-test-tiny-framework-orig-dump.txt
index 8f56f0e219a5..406c61138705 100644
--- a/ravenwood/tools/hoststubgen/test-tiny-framework/golden-output/01-hoststubgen-test-tiny-framework-orig-dump.txt
+++ b/ravenwood/tools/hoststubgen/test-tiny-framework/golden-output/01-hoststubgen-test-tiny-framework-orig-dump.txt
@@ -1883,6 +1883,42 @@ BootstrapMethods:
InnerClasses:
public static #x= #x of #x; // Nested=class com/android/hoststubgen/test/tinyframework/TinyFrameworkLambdas$Nested of class com/android/hoststubgen/test/tinyframework/TinyFrameworkLambdas
public static final #x= #x of #x; // Lookup=class java/lang/invoke/MethodHandles$Lookup of class java/lang/invoke/MethodHandles
+## Class: com/android/hoststubgen/test/tinyframework/TinyFrameworkMethodCallReplace$ConstructorTester.class
+ Compiled from "TinyFrameworkMethodCallReplace.java"
+public class com.android.hoststubgen.test.tinyframework.TinyFrameworkMethodCallReplace$ConstructorTester
+ minor version: 0
+ major version: 61
+ flags: (0x0021) ACC_PUBLIC, ACC_SUPER
+ this_class: #x // com/android/hoststubgen/test/tinyframework/TinyFrameworkMethodCallReplace$ConstructorTester
+ super_class: #x // java/lang/Object
+ interfaces: 0, fields: 1, methods: 1, attributes: 3
+Constant pool:
+{
+ public int i;
+ descriptor: I
+ flags: (0x0001) ACC_PUBLIC
+
+ public com.android.hoststubgen.test.tinyframework.TinyFrameworkMethodCallReplace$ConstructorTester(int);
+ descriptor: (I)V
+ flags: (0x0001) ACC_PUBLIC
+ Code:
+ stack=2, locals=2, args_size=2
+ x: aload_0
+ x: invokespecial #x // Method java/lang/Object."<init>":()V
+ x: aload_0
+ x: iload_1
+ x: putfield #x // Field i:I
+ x: return
+ LineNumberTable:
+ LocalVariableTable:
+ Start Length Slot Name Signature
+ 0 10 0 this Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkMethodCallReplace$ConstructorTester;
+ 0 10 1 i I
+}
+SourceFile: "TinyFrameworkMethodCallReplace.java"
+NestHost: class com/android/hoststubgen/test/tinyframework/TinyFrameworkMethodCallReplace
+InnerClasses:
+ public static #x= #x of #x; // ConstructorTester=class com/android/hoststubgen/test/tinyframework/TinyFrameworkMethodCallReplace$ConstructorTester of class com/android/hoststubgen/test/tinyframework/TinyFrameworkMethodCallReplace
## Class: com/android/hoststubgen/test/tinyframework/TinyFrameworkMethodCallReplace$ReplaceTo.class
Compiled from "TinyFrameworkMethodCallReplace.java"
public class com.android.hoststubgen.test.tinyframework.TinyFrameworkMethodCallReplace$ReplaceTo
@@ -1891,7 +1927,7 @@ public class com.android.hoststubgen.test.tinyframework.TinyFrameworkMethodCallR
flags: (0x0021) ACC_PUBLIC, ACC_SUPER
this_class: #x // com/android/hoststubgen/test/tinyframework/TinyFrameworkMethodCallReplace$ReplaceTo
super_class: #x // java/lang/Object
- interfaces: 0, fields: 0, methods: 3, attributes: 3
+ interfaces: 0, fields: 0, methods: 4, attributes: 3
Constant pool:
{
public com.android.hoststubgen.test.tinyframework.TinyFrameworkMethodCallReplace$ReplaceTo();
@@ -1937,10 +1973,28 @@ Constant pool:
Start Length Slot Name Signature
0 4 0 a I
0 4 1 b I
+
+ public static com.android.hoststubgen.test.tinyframework.TinyFrameworkMethodCallReplace$ConstructorTester newConstructorTester(int);
+ descriptor: (I)Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkMethodCallReplace$ConstructorTester;
+ flags: (0x0009) ACC_PUBLIC, ACC_STATIC
+ Code:
+ stack=4, locals=1, args_size=1
+ x: new #x // class com/android/hoststubgen/test/tinyframework/TinyFrameworkMethodCallReplace$ConstructorTester
+ x: dup
+ x: iload_0
+ x: iconst_1
+ x: iadd
+ x: invokespecial #x // Method com/android/hoststubgen/test/tinyframework/TinyFrameworkMethodCallReplace$ConstructorTester."<init>":(I)V
+ x: areturn
+ LineNumberTable:
+ LocalVariableTable:
+ Start Length Slot Name Signature
+ 0 11 0 i I
}
SourceFile: "TinyFrameworkMethodCallReplace.java"
NestHost: class com/android/hoststubgen/test/tinyframework/TinyFrameworkMethodCallReplace
InnerClasses:
+ public static #x= #x of #x; // ConstructorTester=class com/android/hoststubgen/test/tinyframework/TinyFrameworkMethodCallReplace$ConstructorTester of class com/android/hoststubgen/test/tinyframework/TinyFrameworkMethodCallReplace
public static #x= #x of #x; // ReplaceTo=class com/android/hoststubgen/test/tinyframework/TinyFrameworkMethodCallReplace$ReplaceTo of class com/android/hoststubgen/test/tinyframework/TinyFrameworkMethodCallReplace
## Class: com/android/hoststubgen/test/tinyframework/TinyFrameworkMethodCallReplace.class
Compiled from "TinyFrameworkMethodCallReplace.java"
@@ -1950,7 +2004,7 @@ public class com.android.hoststubgen.test.tinyframework.TinyFrameworkMethodCallR
flags: (0x0021) ACC_PUBLIC, ACC_SUPER
this_class: #x // com/android/hoststubgen/test/tinyframework/TinyFrameworkMethodCallReplace
super_class: #x // java/lang/Object
- interfaces: 0, fields: 0, methods: 5, attributes: 5
+ interfaces: 0, fields: 0, methods: 6, attributes: 5
Constant pool:
{
public com.android.hoststubgen.test.tinyframework.TinyFrameworkMethodCallReplace();
@@ -2008,6 +2062,21 @@ Constant pool:
x: ireturn
LineNumberTable:
+ public static com.android.hoststubgen.test.tinyframework.TinyFrameworkMethodCallReplace$ConstructorTester constructorReplaceTester(int);
+ descriptor: (I)Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkMethodCallReplace$ConstructorTester;
+ flags: (0x0009) ACC_PUBLIC, ACC_STATIC
+ Code:
+ stack=3, locals=1, args_size=1
+ x: new #x // class com/android/hoststubgen/test/tinyframework/TinyFrameworkMethodCallReplace$ConstructorTester
+ x: dup
+ x: iload_0
+ x: invokespecial #x // Method com/android/hoststubgen/test/tinyframework/TinyFrameworkMethodCallReplace$ConstructorTester."<init>":(I)V
+ x: areturn
+ LineNumberTable:
+ LocalVariableTable:
+ Start Length Slot Name Signature
+ 0 9 0 i I
+
private static int originalAdd(int, int);
descriptor: (II)I
flags: (0x000a) ACC_PRIVATE, ACC_STATIC
@@ -2046,6 +2115,7 @@ RuntimeInvisibleAnnotations:
android.hosttest.annotation.HostSideTestWholeClassKeep
NestMembers:
com/android/hoststubgen/test/tinyframework/TinyFrameworkMethodCallReplace$ReplaceTo
+ com/android/hoststubgen/test/tinyframework/TinyFrameworkMethodCallReplace$ConstructorTester
BootstrapMethods:
x: #x REF_invokeStatic java/lang/invoke/LambdaMetafactory.metafactory:(Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/invoke/MethodType;Ljava/lang/invoke/MethodType;Ljava/lang/invoke/MethodHandle;Ljava/lang/invoke/MethodType;)Ljava/lang/invoke/CallSite;
Method arguments:
@@ -2053,8 +2123,9 @@ BootstrapMethods:
#x REF_invokeStatic com/android/hoststubgen/test/tinyframework/TinyFrameworkMethodCallReplace.lambda$nonStaticMethodCallReplaceTester$0:(Ljava/util/concurrent/atomic/AtomicBoolean;)V
#x ()V
InnerClasses:
+ public static #x= #x of #x; // ConstructorTester=class com/android/hoststubgen/test/tinyframework/TinyFrameworkMethodCallReplace$ConstructorTester of class com/android/hoststubgen/test/tinyframework/TinyFrameworkMethodCallReplace
public static #x= #x of #x; // ReplaceTo=class com/android/hoststubgen/test/tinyframework/TinyFrameworkMethodCallReplace$ReplaceTo of class com/android/hoststubgen/test/tinyframework/TinyFrameworkMethodCallReplace
- public static final #x= #x of #x; // Lookup=class java/lang/invoke/MethodHandles$Lookup of class java/lang/invoke/MethodHandles
+ public static final #x= #x of #x; // Lookup=class java/lang/invoke/MethodHandles$Lookup of class java/lang/invoke/MethodHandles
## Class: com/android/hoststubgen/test/tinyframework/TinyFrameworkNative.class
Compiled from "TinyFrameworkNative.java"
public class com.android.hoststubgen.test.tinyframework.TinyFrameworkNative
diff --git a/ravenwood/tools/hoststubgen/test-tiny-framework/golden-output/03-hoststubgen-test-tiny-framework-host-dump.txt b/ravenwood/tools/hoststubgen/test-tiny-framework/golden-output/03-hoststubgen-test-tiny-framework-host-dump.txt
index c918bf86fa40..6a8e4885d1d0 100644
--- a/ravenwood/tools/hoststubgen/test-tiny-framework/golden-output/03-hoststubgen-test-tiny-framework-host-dump.txt
+++ b/ravenwood/tools/hoststubgen/test-tiny-framework/golden-output/03-hoststubgen-test-tiny-framework-host-dump.txt
@@ -2002,6 +2002,51 @@ BootstrapMethods:
#x ()Ljava/lang/Integer;
NestMembers:
com/android/hoststubgen/test/tinyframework/TinyFrameworkLambdas$Nested
+## Class: com/android/hoststubgen/test/tinyframework/TinyFrameworkMethodCallReplace$ConstructorTester.class
+ Compiled from "TinyFrameworkMethodCallReplace.java"
+public class com.android.hoststubgen.test.tinyframework.TinyFrameworkMethodCallReplace$ConstructorTester
+ minor version: 0
+ major version: 61
+ flags: (0x0021) ACC_PUBLIC, ACC_SUPER
+ this_class: #x // com/android/hoststubgen/test/tinyframework/TinyFrameworkMethodCallReplace$ConstructorTester
+ super_class: #x // java/lang/Object
+ interfaces: 0, fields: 1, methods: 1, attributes: 4
+Constant pool:
+{
+ public int i;
+ descriptor: I
+ flags: (0x0001) ACC_PUBLIC
+ RuntimeVisibleAnnotations:
+ x: #x()
+ com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
+
+ public com.android.hoststubgen.test.tinyframework.TinyFrameworkMethodCallReplace$ConstructorTester(int);
+ descriptor: (I)V
+ flags: (0x0001) ACC_PUBLIC
+ Code:
+ stack=2, locals=2, args_size=2
+ x: aload_0
+ x: invokespecial #x // Method java/lang/Object."<init>":()V
+ x: aload_0
+ x: iload_1
+ x: putfield #x // Field i:I
+ x: return
+ LineNumberTable:
+ LocalVariableTable:
+ Start Length Slot Name Signature
+ 0 10 0 this Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkMethodCallReplace$ConstructorTester;
+ 0 10 1 i I
+ RuntimeVisibleAnnotations:
+ x: #x()
+ com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
+}
+InnerClasses:
+ public static #x= #x of #x; // ConstructorTester=class com/android/hoststubgen/test/tinyframework/TinyFrameworkMethodCallReplace$ConstructorTester of class com/android/hoststubgen/test/tinyframework/TinyFrameworkMethodCallReplace
+SourceFile: "TinyFrameworkMethodCallReplace.java"
+RuntimeVisibleAnnotations:
+ x: #x()
+ com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
+NestHost: class com/android/hoststubgen/test/tinyframework/TinyFrameworkMethodCallReplace
## Class: com/android/hoststubgen/test/tinyframework/TinyFrameworkMethodCallReplace$ReplaceTo.class
Compiled from "TinyFrameworkMethodCallReplace.java"
public class com.android.hoststubgen.test.tinyframework.TinyFrameworkMethodCallReplace$ReplaceTo
@@ -2010,7 +2055,7 @@ public class com.android.hoststubgen.test.tinyframework.TinyFrameworkMethodCallR
flags: (0x0021) ACC_PUBLIC, ACC_SUPER
this_class: #x // com/android/hoststubgen/test/tinyframework/TinyFrameworkMethodCallReplace$ReplaceTo
super_class: #x // java/lang/Object
- interfaces: 0, fields: 0, methods: 3, attributes: 4
+ interfaces: 0, fields: 0, methods: 4, attributes: 4
Constant pool:
{
public com.android.hoststubgen.test.tinyframework.TinyFrameworkMethodCallReplace$ReplaceTo();
@@ -2065,9 +2110,30 @@ Constant pool:
RuntimeVisibleAnnotations:
x: #x()
com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
+
+ public static com.android.hoststubgen.test.tinyframework.TinyFrameworkMethodCallReplace$ConstructorTester newConstructorTester(int);
+ descriptor: (I)Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkMethodCallReplace$ConstructorTester;
+ flags: (0x0009) ACC_PUBLIC, ACC_STATIC
+ Code:
+ stack=4, locals=1, args_size=1
+ x: new #x // class com/android/hoststubgen/test/tinyframework/TinyFrameworkMethodCallReplace$ConstructorTester
+ x: dup
+ x: iload_0
+ x: iconst_1
+ x: iadd
+ x: invokespecial #x // Method com/android/hoststubgen/test/tinyframework/TinyFrameworkMethodCallReplace$ConstructorTester."<init>":(I)V
+ x: areturn
+ LineNumberTable:
+ LocalVariableTable:
+ Start Length Slot Name Signature
+ 0 11 0 i I
+ RuntimeVisibleAnnotations:
+ x: #x()
+ com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
}
InnerClasses:
- public static #x= #x of #x; // ReplaceTo=class com/android/hoststubgen/test/tinyframework/TinyFrameworkMethodCallReplace$ReplaceTo of class com/android/hoststubgen/test/tinyframework/TinyFrameworkMethodCallReplace
+ public static #x= #x of #x; // ConstructorTester=class com/android/hoststubgen/test/tinyframework/TinyFrameworkMethodCallReplace$ConstructorTester of class com/android/hoststubgen/test/tinyframework/TinyFrameworkMethodCallReplace
+ public static #x= #x of #x; // ReplaceTo=class com/android/hoststubgen/test/tinyframework/TinyFrameworkMethodCallReplace$ReplaceTo of class com/android/hoststubgen/test/tinyframework/TinyFrameworkMethodCallReplace
SourceFile: "TinyFrameworkMethodCallReplace.java"
RuntimeVisibleAnnotations:
x: #x()
@@ -2081,7 +2147,7 @@ public class com.android.hoststubgen.test.tinyframework.TinyFrameworkMethodCallR
flags: (0x0021) ACC_PUBLIC, ACC_SUPER
this_class: #x // com/android/hoststubgen/test/tinyframework/TinyFrameworkMethodCallReplace
super_class: #x // java/lang/Object
- interfaces: 0, fields: 0, methods: 4, attributes: 6
+ interfaces: 0, fields: 0, methods: 6, attributes: 6
Constant pool:
{
public com.android.hoststubgen.test.tinyframework.TinyFrameworkMethodCallReplace();
@@ -2148,6 +2214,48 @@ Constant pool:
x: #x()
com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
+ public static com.android.hoststubgen.test.tinyframework.TinyFrameworkMethodCallReplace$ConstructorTester constructorReplaceTester(int);
+ descriptor: (I)Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkMethodCallReplace$ConstructorTester;
+ flags: (0x0009) ACC_PUBLIC, ACC_STATIC
+ Code:
+ stack=3, locals=1, args_size=1
+ x: new #x // class com/android/hoststubgen/test/tinyframework/TinyFrameworkMethodCallReplace$ConstructorTester
+ x: dup
+ x: iload_0
+ x: invokestatic #x // Method com/android/hoststubgen/test/tinyframework/TinyFrameworkMethodCallReplace$ReplaceTo.newConstructorTester:(I)Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkMethodCallReplace$ConstructorTester;
+ x: swap
+ x: pop
+ x: swap
+ x: pop
+ x: areturn
+ LineNumberTable:
+ LocalVariableTable:
+ Start Length Slot Name Signature
+ 0 13 0 i I
+ RuntimeVisibleAnnotations:
+ x: #x()
+ com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
+
+ private static int originalAdd(int, int);
+ descriptor: (II)I
+ flags: (0x000a) ACC_PRIVATE, ACC_STATIC
+ Code:
+ stack=2, locals=2, args_size=2
+ x: iload_0
+ x: iload_1
+ x: iadd
+ x: iconst_1
+ x: isub
+ x: ireturn
+ LineNumberTable:
+ LocalVariableTable:
+ Start Length Slot Name Signature
+ 0 6 0 a I
+ 0 6 1 b I
+ RuntimeVisibleAnnotations:
+ x: #x()
+ com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
+
private static void lambda$nonStaticMethodCallReplaceTester$0(java.util.concurrent.atomic.AtomicBoolean);
descriptor: (Ljava/util/concurrent/atomic/AtomicBoolean;)V
flags: (0x100a) ACC_PRIVATE, ACC_STATIC, ACC_SYNTHETIC
@@ -2167,6 +2275,7 @@ Constant pool:
com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
}
InnerClasses:
+ public static #x= #x of #x; // ConstructorTester=class com/android/hoststubgen/test/tinyframework/TinyFrameworkMethodCallReplace$ConstructorTester of class com/android/hoststubgen/test/tinyframework/TinyFrameworkMethodCallReplace
public static #x= #x of #x; // ReplaceTo=class com/android/hoststubgen/test/tinyframework/TinyFrameworkMethodCallReplace$ReplaceTo of class com/android/hoststubgen/test/tinyframework/TinyFrameworkMethodCallReplace
public static final #x= #x of #x; // Lookup=class java/lang/invoke/MethodHandles$Lookup of class java/lang/invoke/MethodHandles
SourceFile: "TinyFrameworkMethodCallReplace.java"
@@ -2184,6 +2293,7 @@ BootstrapMethods:
#x ()V
NestMembers:
com/android/hoststubgen/test/tinyframework/TinyFrameworkMethodCallReplace$ReplaceTo
+ com/android/hoststubgen/test/tinyframework/TinyFrameworkMethodCallReplace$ConstructorTester
## Class: com/android/hoststubgen/test/tinyframework/TinyFrameworkNative.class
Compiled from "TinyFrameworkNative.java"
public class com.android.hoststubgen.test.tinyframework.TinyFrameworkNative
diff --git a/ravenwood/tools/hoststubgen/test-tiny-framework/golden-output/13-hoststubgen-test-tiny-framework-host-ext-dump.txt b/ravenwood/tools/hoststubgen/test-tiny-framework/golden-output/13-hoststubgen-test-tiny-framework-host-ext-dump.txt
index 28065bf16d01..d8e76321b038 100644
--- a/ravenwood/tools/hoststubgen/test-tiny-framework/golden-output/13-hoststubgen-test-tiny-framework-host-ext-dump.txt
+++ b/ravenwood/tools/hoststubgen/test-tiny-framework/golden-output/13-hoststubgen-test-tiny-framework-host-ext-dump.txt
@@ -2413,6 +2413,66 @@ BootstrapMethods:
#x ()Ljava/lang/Integer;
NestMembers:
com/android/hoststubgen/test/tinyframework/TinyFrameworkLambdas$Nested
+## Class: com/android/hoststubgen/test/tinyframework/TinyFrameworkMethodCallReplace$ConstructorTester.class
+ Compiled from "TinyFrameworkMethodCallReplace.java"
+public class com.android.hoststubgen.test.tinyframework.TinyFrameworkMethodCallReplace$ConstructorTester
+ minor version: 0
+ major version: 61
+ flags: (0x0021) ACC_PUBLIC, ACC_SUPER
+ this_class: #x // com/android/hoststubgen/test/tinyframework/TinyFrameworkMethodCallReplace$ConstructorTester
+ super_class: #x // java/lang/Object
+ interfaces: 0, fields: 1, methods: 2, attributes: 4
+Constant pool:
+{
+ public int i;
+ descriptor: I
+ flags: (0x0001) ACC_PUBLIC
+ RuntimeVisibleAnnotations:
+ x: #x()
+ com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
+
+ private static {};
+ descriptor: ()V
+ flags: (0x000a) ACC_PRIVATE, ACC_STATIC
+ Code:
+ stack=2, locals=0, args_size=0
+ x: ldc #x // class com/android/hoststubgen/test/tinyframework/TinyFrameworkMethodCallReplace$ConstructorTester
+ x: ldc #x // String com.android.hoststubgen.hosthelper.HostTestUtils.logClassLoaded
+ x: invokestatic #x // Method com/android/hoststubgen/hosthelper/HostTestUtils.onClassLoaded:(Ljava/lang/Class;Ljava/lang/String;)V
+ x: return
+
+ public com.android.hoststubgen.test.tinyframework.TinyFrameworkMethodCallReplace$ConstructorTester(int);
+ descriptor: (I)V
+ flags: (0x0001) ACC_PUBLIC
+ Code:
+ stack=4, locals=2, args_size=2
+ x: ldc #x // class com/android/hoststubgen/test/tinyframework/TinyFrameworkMethodCallReplace$ConstructorTester
+ x: ldc #x // String <init>
+ x: ldc #x // String (I)V
+ x: ldc #x // String com.android.hoststubgen.hosthelper.HostTestUtils.logMethodCall
+ x: invokestatic #x // Method com/android/hoststubgen/hosthelper/HostTestUtils.callMethodCallHook:(Ljava/lang/Class;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V
+ x: aload_0
+ x: invokespecial #x // Method java/lang/Object."<init>":()V
+ x: aload_0
+ x: iload_1
+ x: putfield #x // Field i:I
+ x: return
+ LineNumberTable:
+ LocalVariableTable:
+ Start Length Slot Name Signature
+ 11 10 0 this Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkMethodCallReplace$ConstructorTester;
+ 11 10 1 i I
+ RuntimeVisibleAnnotations:
+ x: #x()
+ com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
+}
+InnerClasses:
+ public static #x= #x of #x; // ConstructorTester=class com/android/hoststubgen/test/tinyframework/TinyFrameworkMethodCallReplace$ConstructorTester of class com/android/hoststubgen/test/tinyframework/TinyFrameworkMethodCallReplace
+SourceFile: "TinyFrameworkMethodCallReplace.java"
+RuntimeVisibleAnnotations:
+ x: #x()
+ com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
+NestHost: class com/android/hoststubgen/test/tinyframework/TinyFrameworkMethodCallReplace
## Class: com/android/hoststubgen/test/tinyframework/TinyFrameworkMethodCallReplace$ReplaceTo.class
Compiled from "TinyFrameworkMethodCallReplace.java"
public class com.android.hoststubgen.test.tinyframework.TinyFrameworkMethodCallReplace$ReplaceTo
@@ -2421,7 +2481,7 @@ public class com.android.hoststubgen.test.tinyframework.TinyFrameworkMethodCallR
flags: (0x0021) ACC_PUBLIC, ACC_SUPER
this_class: #x // com/android/hoststubgen/test/tinyframework/TinyFrameworkMethodCallReplace$ReplaceTo
super_class: #x // java/lang/Object
- interfaces: 0, fields: 0, methods: 4, attributes: 4
+ interfaces: 0, fields: 0, methods: 5, attributes: 4
Constant pool:
{
private static {};
@@ -2501,8 +2561,34 @@ Constant pool:
RuntimeVisibleAnnotations:
x: #x()
com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
+
+ public static com.android.hoststubgen.test.tinyframework.TinyFrameworkMethodCallReplace$ConstructorTester newConstructorTester(int);
+ descriptor: (I)Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkMethodCallReplace$ConstructorTester;
+ flags: (0x0009) ACC_PUBLIC, ACC_STATIC
+ Code:
+ stack=4, locals=1, args_size=1
+ x: ldc #x // class com/android/hoststubgen/test/tinyframework/TinyFrameworkMethodCallReplace$ReplaceTo
+ x: ldc #x // String newConstructorTester
+ x: ldc #x // String (I)Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkMethodCallReplace$ConstructorTester;
+ x: ldc #x // String com.android.hoststubgen.hosthelper.HostTestUtils.logMethodCall
+ x: invokestatic #x // Method com/android/hoststubgen/hosthelper/HostTestUtils.callMethodCallHook:(Ljava/lang/Class;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V
+ x: new #x // class com/android/hoststubgen/test/tinyframework/TinyFrameworkMethodCallReplace$ConstructorTester
+ x: dup
+ x: iload_0
+ x: iconst_1
+ x: iadd
+ x: invokespecial #x // Method com/android/hoststubgen/test/tinyframework/TinyFrameworkMethodCallReplace$ConstructorTester."<init>":(I)V
+ x: areturn
+ LineNumberTable:
+ LocalVariableTable:
+ Start Length Slot Name Signature
+ 11 11 0 i I
+ RuntimeVisibleAnnotations:
+ x: #x()
+ com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
}
InnerClasses:
+ public static #x= #x of #x; // ConstructorTester=class com/android/hoststubgen/test/tinyframework/TinyFrameworkMethodCallReplace$ConstructorTester of class com/android/hoststubgen/test/tinyframework/TinyFrameworkMethodCallReplace
public static #x= #x of #x; // ReplaceTo=class com/android/hoststubgen/test/tinyframework/TinyFrameworkMethodCallReplace$ReplaceTo of class com/android/hoststubgen/test/tinyframework/TinyFrameworkMethodCallReplace
SourceFile: "TinyFrameworkMethodCallReplace.java"
RuntimeVisibleAnnotations:
@@ -2517,7 +2603,7 @@ public class com.android.hoststubgen.test.tinyframework.TinyFrameworkMethodCallR
flags: (0x0021) ACC_PUBLIC, ACC_SUPER
this_class: #x // com/android/hoststubgen/test/tinyframework/TinyFrameworkMethodCallReplace
super_class: #x // java/lang/Object
- interfaces: 0, fields: 0, methods: 5, attributes: 6
+ interfaces: 0, fields: 0, methods: 7, attributes: 6
Constant pool:
{
private static {};
@@ -2609,18 +2695,70 @@ Constant pool:
x: #x()
com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
+ public static com.android.hoststubgen.test.tinyframework.TinyFrameworkMethodCallReplace$ConstructorTester constructorReplaceTester(int);
+ descriptor: (I)Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkMethodCallReplace$ConstructorTester;
+ flags: (0x0009) ACC_PUBLIC, ACC_STATIC
+ Code:
+ stack=4, locals=1, args_size=1
+ x: ldc #x // class com/android/hoststubgen/test/tinyframework/TinyFrameworkMethodCallReplace
+ x: ldc #x // String constructorReplaceTester
+ x: ldc #x // String (I)Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkMethodCallReplace$ConstructorTester;
+ x: ldc #x // String com.android.hoststubgen.hosthelper.HostTestUtils.logMethodCall
+ x: invokestatic #x // Method com/android/hoststubgen/hosthelper/HostTestUtils.callMethodCallHook:(Ljava/lang/Class;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V
+ x: new #x // class com/android/hoststubgen/test/tinyframework/TinyFrameworkMethodCallReplace$ConstructorTester
+ x: dup
+ x: iload_0
+ x: invokestatic #x // Method com/android/hoststubgen/test/tinyframework/TinyFrameworkMethodCallReplace$ReplaceTo.newConstructorTester:(I)Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkMethodCallReplace$ConstructorTester;
+ x: swap
+ x: pop
+ x: swap
+ x: pop
+ x: areturn
+ LineNumberTable:
+ LocalVariableTable:
+ Start Length Slot Name Signature
+ 11 13 0 i I
+ RuntimeVisibleAnnotations:
+ x: #x()
+ com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
+
+ private static int originalAdd(int, int);
+ descriptor: (II)I
+ flags: (0x000a) ACC_PRIVATE, ACC_STATIC
+ Code:
+ stack=4, locals=2, args_size=2
+ x: ldc #x // class com/android/hoststubgen/test/tinyframework/TinyFrameworkMethodCallReplace
+ x: ldc #x // String originalAdd
+ x: ldc #x // String (II)I
+ x: ldc #x // String com.android.hoststubgen.hosthelper.HostTestUtils.logMethodCall
+ x: invokestatic #x // Method com/android/hoststubgen/hosthelper/HostTestUtils.callMethodCallHook:(Ljava/lang/Class;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V
+ x: iload_0
+ x: iload_1
+ x: iadd
+ x: iconst_1
+ x: isub
+ x: ireturn
+ LineNumberTable:
+ LocalVariableTable:
+ Start Length Slot Name Signature
+ 11 6 0 a I
+ 11 6 1 b I
+ RuntimeVisibleAnnotations:
+ x: #x()
+ com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
+
private static void lambda$nonStaticMethodCallReplaceTester$0(java.util.concurrent.atomic.AtomicBoolean);
descriptor: (Ljava/util/concurrent/atomic/AtomicBoolean;)V
flags: (0x100a) ACC_PRIVATE, ACC_STATIC, ACC_SYNTHETIC
Code:
stack=4, locals=1, args_size=1
x: ldc #x // class com/android/hoststubgen/test/tinyframework/TinyFrameworkMethodCallReplace
- x: ldc #x // String lambda$nonStaticMethodCallReplaceTester$0
- x: ldc #x // String (Ljava/util/concurrent/atomic/AtomicBoolean;)V
+ x: ldc #x // String lambda$nonStaticMethodCallReplaceTester$0
+ x: ldc #x // String (Ljava/util/concurrent/atomic/AtomicBoolean;)V
x: ldc #x // String com.android.hoststubgen.hosthelper.HostTestUtils.logMethodCall
x: invokestatic #x // Method com/android/hoststubgen/hosthelper/HostTestUtils.callMethodCallHook:(Ljava/lang/Class;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V
x: aload_0
- x: invokestatic #x // Method java/lang/Thread.currentThread:()Ljava/lang/Thread;
+ x: invokestatic #x // Method java/lang/Thread.currentThread:()Ljava/lang/Thread;
x: invokevirtual #x // Method java/lang/Thread.isDaemon:()Z
x: invokevirtual #x // Method java/util/concurrent/atomic/AtomicBoolean.set:(Z)V
x: return
@@ -2633,6 +2771,7 @@ Constant pool:
com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
}
InnerClasses:
+ public static #x= #x of #x; // ConstructorTester=class com/android/hoststubgen/test/tinyframework/TinyFrameworkMethodCallReplace$ConstructorTester of class com/android/hoststubgen/test/tinyframework/TinyFrameworkMethodCallReplace
public static #x= #x of #x; // ReplaceTo=class com/android/hoststubgen/test/tinyframework/TinyFrameworkMethodCallReplace$ReplaceTo of class com/android/hoststubgen/test/tinyframework/TinyFrameworkMethodCallReplace
public static final #x= #x of #x; // Lookup=class java/lang/invoke/MethodHandles$Lookup of class java/lang/invoke/MethodHandles
SourceFile: "TinyFrameworkMethodCallReplace.java"
@@ -2650,6 +2789,7 @@ BootstrapMethods:
#x ()V
NestMembers:
com/android/hoststubgen/test/tinyframework/TinyFrameworkMethodCallReplace$ReplaceTo
+ com/android/hoststubgen/test/tinyframework/TinyFrameworkMethodCallReplace$ConstructorTester
## Class: com/android/hoststubgen/test/tinyframework/TinyFrameworkNative.class
Compiled from "TinyFrameworkNative.java"
public class com.android.hoststubgen.test.tinyframework.TinyFrameworkNative
diff --git a/ravenwood/tools/hoststubgen/test-tiny-framework/policy-override-tiny-framework.txt b/ravenwood/tools/hoststubgen/test-tiny-framework/policy-override-tiny-framework.txt
index 1f64a3c78c53..cbaad2e85717 100644
--- a/ravenwood/tools/hoststubgen/test-tiny-framework/policy-override-tiny-framework.txt
+++ b/ravenwood/tools/hoststubgen/test-tiny-framework/policy-override-tiny-framework.txt
@@ -68,6 +68,10 @@ class com.android.hoststubgen.test.tinyframework.TinyFrameworkMethodCallReplace
class java.lang.Thread keep
method start ()V @com.android.hoststubgen.test.tinyframework.TinyFrameworkMethodCallReplace$ReplaceTo.startThread
+# Used to test constructor replacement.
+class com.android.hoststubgen.test.tinyframework.TinyFrameworkMethodCallReplace$ConstructorTester keepclass
+ method <init> (I)V @com.android.hoststubgen.test.tinyframework.TinyFrameworkMethodCallReplace$ReplaceTo.newConstructorTester
+
# "rename" takes a type internal name, so '/'s is used as a separator.
# The leading / in the prefix is not needed (it'll be stripped), but it's added to make
diff --git a/ravenwood/tools/hoststubgen/test-tiny-framework/tiny-framework/src/com/android/hoststubgen/test/tinyframework/TinyFrameworkMethodCallReplace.java b/ravenwood/tools/hoststubgen/test-tiny-framework/tiny-framework/src/com/android/hoststubgen/test/tinyframework/TinyFrameworkMethodCallReplace.java
index 57c69a336654..d850be82719f 100644
--- a/ravenwood/tools/hoststubgen/test-tiny-framework/tiny-framework/src/com/android/hoststubgen/test/tinyframework/TinyFrameworkMethodCallReplace.java
+++ b/ravenwood/tools/hoststubgen/test-tiny-framework/tiny-framework/src/com/android/hoststubgen/test/tinyframework/TinyFrameworkMethodCallReplace.java
@@ -41,10 +41,23 @@ public class TinyFrameworkMethodCallReplace {
return originalAdd(1, 2);
}
+ public static ConstructorTester constructorReplaceTester(int i) {
+ // This object construction will be replaced with ReplaceTo.newConstructorTester().
+ return new ConstructorTester(i);
+ }
+
private static int originalAdd(int a, int b) {
return a + b - 1; // Original is broken.
}
+ public static class ConstructorTester {
+ public int i;
+
+ public ConstructorTester(int i) {
+ this.i = i;
+ }
+ }
+
public static class ReplaceTo {
public static void startThread(Thread thread) {
thread.setDaemon(true);
@@ -54,5 +67,9 @@ public class TinyFrameworkMethodCallReplace {
public static int add(int a, int b) {
return a + b;
}
+
+ public static ConstructorTester newConstructorTester(int i) {
+ return new ConstructorTester(i + 1);
+ }
}
}
diff --git a/ravenwood/tools/hoststubgen/test-tiny-framework/tiny-test/src/com/android/hoststubgen/test/tinyframework/TinyFrameworkClassTest.java b/ravenwood/tools/hoststubgen/test-tiny-framework/tiny-test/src/com/android/hoststubgen/test/tinyframework/TinyFrameworkClassTest.java
index b8d6be4265c9..89fcd30b3df5 100644
--- a/ravenwood/tools/hoststubgen/test-tiny-framework/tiny-test/src/com/android/hoststubgen/test/tinyframework/TinyFrameworkClassTest.java
+++ b/ravenwood/tools/hoststubgen/test-tiny-framework/tiny-test/src/com/android/hoststubgen/test/tinyframework/TinyFrameworkClassTest.java
@@ -323,4 +323,10 @@ public class TinyFrameworkClassTest {
assertThat(TinyFrameworkMethodCallReplace.staticMethodCallReplaceTester())
.isEqualTo(3);
}
+
+ @Test
+ public void testConstructorCallReplace() throws Exception {
+ assertThat(TinyFrameworkMethodCallReplace.constructorReplaceTester(5).i)
+ .isEqualTo(6);
+ }
}
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..f7fd0804c151 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
/**
* 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, ai: ArgIterator): Boolean {
+ fun nextArg(): String = ai.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 b6089eaff1ed..fd6f732a06ce 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
@@ -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")
@@ -449,7 +449,6 @@ private class TextPolicyToAnnotationConverter(
methodName: String,
methodDesc: String,
replaceSpec: TextFilePolicyMethodReplaceFilter.MethodCallReplaceSpec,
- policy: FilterPolicyWithReason
) {
// 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..8b95843f08a6 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
/**
* 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, ai: ArgIterator): Boolean {
+ fun nextArg(): String = ai.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..957e20647d44 100644
--- a/ravenwood/tools/ravenizer/Android.bp
+++ b/ravenwood/tools/ravenizer/Android.bp
@@ -13,12 +13,6 @@ 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",
],
visibility: ["//visibility:public"],
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..7f4829ec6127 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,21 @@ import com.android.hoststubgen.LogLevel
import com.android.hoststubgen.executableName
import com.android.hoststubgen.log
import com.android.hoststubgen.runMainWithBoilerplate
+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"
+
/**
* Entry point.
@@ -30,7 +45,15 @@ fun main(args: Array<String>) {
log.setConsoleLogLevel(LogLevel.Info)
runMainWithBoilerplate {
- val options = RavenizerOptions.parseArgs(args)
+ var newArgs = args.asList()
+ if (Paths.get(RAVENIZER_DOTFILE).exists()) {
+ log.i("Reading options from $RAVENIZER_DOTFILE")
+ newArgs = args.toMutableList().apply {
+ 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..2c0365404ab6 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.utils.ArgIterator
+import com.android.hoststubgen.utils.BaseOptions
+import com.android.hoststubgen.utils.SetOnce
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
+) : BaseOptions() {
+
+ override fun parseOption(option: String, ai: ArgIterator): Boolean {
+ fun nextArg(): String = ai.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 false
+ }
+
+ 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()
}
}
diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
index 42834ce20783..c49151dd5e30 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;
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 bb3c710b0c23..cc93d0887d89 100644
--- a/services/accessibility/java/com/android/server/accessibility/autoclick/AutoclickController.java
+++ b/services/accessibility/java/com/android/server/accessibility/autoclick/AutoclickController.java
@@ -25,6 +25,7 @@ import static android.view.accessibility.AccessibilityManager.AUTOCLICK_IGNORE_M
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 +88,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,17 +100,26 @@ 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
public void toggleAutoclickPause(boolean paused) {
if (paused) {
- if (mClickScheduler != null) {
- mClickScheduler.cancel();
- }
- if (mAutoclickIndicatorScheduler != null) {
- mAutoclickIndicatorScheduler.cancel();
- }
+ cancelPendingClick();
+ }
+ }
+
+ @Override
+ public void onHoverChange(boolean hovered) {
+ // Cancel all pending clicks when the mouse moves outside the panel while
+ // autoclick is still paused.
+ if (!hovered && isPaused()) {
+ cancelPendingClick();
}
}
};
@@ -157,6 +168,7 @@ public class AutoclickController extends BaseEventStreamTransformation {
mWindowManager = mContext.getSystemService(WindowManager.class);
mAutoclickTypePanel =
new AutoclickTypePanel(mContext, mWindowManager, mUserId, clickPanelController);
+ mAutoclickScrollPanel = new AutoclickScrollPanel(mContext, mWindowManager);
mAutoclickTypePanel.show();
mWindowManager.addView(mAutoclickIndicatorView, mAutoclickIndicatorView.getLayoutParams());
@@ -185,6 +197,10 @@ public class AutoclickController extends BaseEventStreamTransformation {
mClickScheduler.cancel();
}
+ if (mAutoclickScrollPanel != null) {
+ mAutoclickScrollPanel.hide();
+ }
+
super.clearEvents(inputSource);
}
@@ -205,6 +221,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) {
@@ -226,8 +247,21 @@ public class AutoclickController extends BaseEventStreamTransformation {
}
private boolean isPaused() {
- // TODO (b/397460424): Unpause when hovering over panel.
- return Flags.enableAutoclickIndicator() && mAutoclickTypePanel.isPaused();
+ return Flags.enableAutoclickIndicator() && mAutoclickTypePanel.isPaused()
+ && !isHovered();
+ }
+
+ private boolean isHovered() {
+ return Flags.enableAutoclickIndicator() && mAutoclickTypePanel.isHovered();
+ }
+
+ private void cancelPendingClick() {
+ if (mClickScheduler != null) {
+ mClickScheduler.cancel();
+ }
+ if (mAutoclickIndicatorScheduler != null) {
+ mAutoclickIndicatorScheduler.cancel();
+ }
}
@VisibleForTesting
@@ -465,6 +499,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.
@@ -618,6 +654,7 @@ public class AutoclickController extends BaseEventStreamTransformation {
}
mLastMotionEvent = MotionEvent.obtain(event);
mEventPolicyFlags = policyFlags;
+ mHoveredState = isHovered();
if (useAsAnchor) {
final int pointerIndex = mLastMotionEvent.getActionIndex();
@@ -674,6 +711,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) {
@@ -691,14 +736,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/AutoclickLinearLayout.java b/services/accessibility/java/com/android/server/accessibility/autoclick/AutoclickLinearLayout.java
new file mode 100644
index 000000000000..fe8adf75704d
--- /dev/null
+++ b/services/accessibility/java/com/android/server/accessibility/autoclick/AutoclickLinearLayout.java
@@ -0,0 +1,80 @@
+/*
+ * 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 android.content.Context;
+import android.util.AttributeSet;
+import android.view.MotionEvent;
+import android.widget.LinearLayout;
+
+/**
+ * A custom LinearLayout that provides enhanced hover event handling.
+ * This class overrides hover methods to track hover events for the entire panel ViewGroup,
+ * including the descendant buttons. This allows for consistent hover behavior and feedback
+ * across the entire layout.
+ */
+public class AutoclickLinearLayout extends LinearLayout {
+ public interface OnHoverChangedListener {
+ /**
+ * Called when the hover state of the AutoclickLinearLayout changes.
+ *
+ * @param hovered {@code true} if the view is now hovered, {@code false} otherwise.
+ */
+ void onHoverChanged(boolean hovered);
+ }
+
+ private OnHoverChangedListener mListener;
+
+ public AutoclickLinearLayout(Context context) {
+ super(context);
+ }
+
+ public AutoclickLinearLayout(Context context, AttributeSet attrs) {
+ super(context, attrs);
+ }
+
+ public AutoclickLinearLayout(Context context, AttributeSet attrs, int defStyleAttr) {
+ super(context, attrs, defStyleAttr);
+ }
+
+ public AutoclickLinearLayout(Context context, AttributeSet attrs, int defStyleAttr,
+ int defStyleRes) {
+ super(context, attrs, defStyleAttr, defStyleRes);
+ }
+
+ public void setOnHoverChangedListener(OnHoverChangedListener listener) {
+ mListener = listener;
+ }
+
+ @Override
+ public boolean onInterceptHoverEvent(MotionEvent event) {
+ int action = event.getActionMasked();
+ setHovered(action == MotionEvent.ACTION_HOVER_ENTER
+ || action == MotionEvent.ACTION_HOVER_MOVE);
+
+ return false;
+ }
+
+ @Override
+ public void onHoverChanged(boolean hovered) {
+ super.onHoverChanged(hovered);
+
+ if (mListener != null) {
+ mListener.onHoverChanged(hovered);
+ }
+ }
+}
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..86f79a83ea28
--- /dev/null
+++ b/services/accessibility/java/com/android/server/accessibility/autoclick/AutoclickScrollPanel.java
@@ -0,0 +1,105 @@
+/*
+ * 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.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 androidx.annotation.NonNull;
+import androidx.annotation.VisibleForTesting;
+
+import com.android.internal.R;
+
+public class AutoclickScrollPanel {
+ private final Context mContext;
+ private final View mContentView;
+ private final WindowManager mWindowManager;
+ private boolean mInScrollMode = false;
+
+ public AutoclickScrollPanel(Context context, WindowManager windowManager) {
+ mContext = context;
+ mWindowManager = windowManager;
+ mContentView = LayoutInflater.from(context).inflate(
+ R.layout.accessibility_autoclick_scroll_panel, null);
+ }
+
+ /**
+ * 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;
+ }
+
+ /**
+ * 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 ab4b3b13eece..90ddc43ae3ed 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 {
@@ -110,11 +103,18 @@ public class AutoclickTypePanel {
* @param paused {@code true} to pause autoclick, {@code false} to resume.
*/
void toggleAutoclickPause(boolean paused);
+
+ /**
+ * Called when the hovered state of the panel changes.
+ *
+ * @param hovered {@code true} if the panel is now hovered, {@code false} otherwise.
+ */
+ void onHoverChange(boolean hovered);
}
private final Context mContext;
- private final View mContentView;
+ private final AutoclickLinearLayout mContentView;
private final WindowManager mWindowManager;
@@ -129,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;
@@ -164,8 +163,9 @@ public class AutoclickTypePanel {
R.drawable.accessibility_autoclick_resume);
mContentView =
- LayoutInflater.from(context)
+ (AutoclickLinearLayout) LayoutInflater.from(context)
.inflate(R.layout.accessibility_autoclick_type_panel, null);
+ mContentView.setOnHoverChangedListener(mClickPanelController::onHoverChange);
mLeftClickButton =
mContentView.findViewById(R.id.accessibility_autoclick_left_click_layout);
mRightClickButton =
@@ -249,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
@@ -339,6 +339,10 @@ public class AutoclickTypePanel {
return mPaused;
}
+ public boolean isHovered() {
+ return mContentView.isHovered();
+ }
+
/** Toggles the panel expanded or collapsed state. */
private void togglePanelExpansion(@AutoclickType int clickType) {
final LinearLayout button = getButtonFromClickType(clickType);
@@ -403,10 +407,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);
}
@@ -445,7 +449,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);
@@ -461,7 +465,7 @@ public class AutoclickTypePanel {
ACCESSIBILITY_AUTOCLICK_PANEL_POSITION, mUserId);
if (savedPosition == null) {
setPanelPositionForCorner(mParams, CORNER_BOTTOM_RIGHT);
- mCurrentCornerIndex = 0;
+ mCurrentCorner = CORNER_BOTTOM_RIGHT;
return;
}
@@ -469,7 +473,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;
}
@@ -477,7 +481,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) {
@@ -520,14 +524,14 @@ public class AutoclickTypePanel {
@VisibleForTesting
@NonNull
- View getContentViewForTesting() {
+ AutoclickLinearLayout getContentViewForTesting() {
return mContentView;
}
@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 fd230f69aec0..fb329430acb2 100644
--- a/services/accessibility/java/com/android/server/accessibility/gestures/TouchExplorer.java
+++ b/services/accessibility/java/com/android/server/accessibility/gestures/TouchExplorer.java
@@ -887,6 +887,9 @@ public class TouchExplorer extends BaseEventStreamTransformation
mSendHoverEnterAndMoveDelayed.cancel();
mSendHoverExitDelayed.cancel();
}
+ if (pointerIndex < 0) {
+ return;
+ }
// If the user is touch exploring the second pointer may be
// performing a double tap to activate an item without need
// for the user to lift their exploring finger.
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/Session.java b/services/autofill/java/com/android/server/autofill/Session.java
index 6fdb2b6b83f7..6515b237519a 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;
@@ -1416,6 +1417,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
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..48d21c3a222f 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,7 +980,7 @@ public class PerformFullTransportBackupTask extends FullBackupTask implements Ba
public void operationComplete(long result) { /* intentionally empty */ }
@Override
- public void handleCancel(boolean cancelAll) {
+ public void handleCancel(@CancellationReason int cancellationReason) {
Slog.w(TAG, "Full backup cancel of " + mTarget.packageName);
mBackupManagerMonitorEventSender.monitorEvent(
@@ -984,7 +990,7 @@ public class PerformFullTransportBackupTask extends FullBackupTask implements Ba
/* extras= */ null);
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/core/Android.bp b/services/core/Android.bp
index 2aaf6a9c2391..14d9d3f0c0a1 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: [
@@ -136,6 +143,7 @@ java_library_static {
":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..aae48daa5dde 100644
--- a/services/core/java/com/android/server/adb/AdbService.java
+++ b/services/core/java/com/android/server/adb/AdbService.java
@@ -222,15 +222,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;
@@ -256,7 +255,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,7 +286,7 @@ 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");
+ Slog.d(TAG, "boot completed");
if (mDebuggingManager != null) {
mDebuggingManager.setAdbEnabled(mIsAdbUsbEnabled, AdbTransportType.USB);
mDebuggingManager.setAdbEnabled(mIsAdbWifiEnabled, AdbTransportType.WIFI);
@@ -429,17 +428,13 @@ public class AdbService extends IAdbManager.Stub {
@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,11 +495,8 @@ 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;
@@ -549,20 +541,14 @@ public class AdbService extends IAdbManager.Stub {
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);
}
});
}
diff --git a/services/core/java/com/android/server/am/ActiveServices.java b/services/core/java/com/android/server/am/ActiveServices.java
index 4187ee274f56..5395d2a914ec 100644
--- a/services/core/java/com/android/server/am/ActiveServices.java
+++ b/services/core/java/com/android/server/am/ActiveServices.java
@@ -2625,6 +2625,13 @@ public final class ActiveServices {
}
notification.flags |= Notification.FLAG_FOREGROUND_SERVICE;
r.foregroundNoti = notification;
+ if (r.isForeground && foregroundServiceType != previousFgsType) {
+ // An already foreground service is being started with a different fgs type
+ // which results in the type changing without typical startForeground
+ // logging.
+ Slog.w(TAG_SERVICE, "FGS type change for " + r.shortInstanceName
+ + " from " + previousFgsType + " to " + foregroundServiceType);
+ }
mAm.mProcessStateController.setForegroundServiceType(r, foregroundServiceType);
if (!r.isForeground) {
final ServiceMap smap = getServiceMapLocked(r.userId);
@@ -9191,7 +9198,9 @@ public final class ActiveServices {
} else {
synchronized (mAm.mPidsSelfLocked) {
callerApp = mAm.mPidsSelfLocked.get(callingPid);
- caller = callerApp.getThread();
+ if (callerApp != null) {
+ caller = callerApp.getThread();
+ }
}
}
if (callerApp == null) {
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/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..508c01802156 100644
--- a/services/core/java/com/android/server/am/BroadcastProcessQueue.java
+++ b/services/core/java/com/android/server/am/BroadcastProcessQueue.java
@@ -810,7 +810,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 +818,7 @@ class BroadcastProcessQueue {
* being actively dispatched in this process.
*/
public int getActiveIndex() {
- Objects.requireNonNull(mActive);
+ Objects.requireNonNull(mActive, toString());
return mActiveIndex;
}
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..d276b9a94791 100644
--- a/services/core/java/com/android/server/am/BroadcastQueueImpl.java
+++ b/services/core/java/com/android/server/am/BroadcastQueueImpl.java
@@ -606,8 +606,9 @@ class BroadcastQueueImpl extends BroadcastQueue {
} else {
mRunningColdStart.reEnqueueActiveBroadcast();
}
- demoteFromRunningLocked(mRunningColdStart);
+ final BroadcastProcessQueue queue = mRunningColdStart;
clearRunningColdStart();
+ demoteFromRunningLocked(queue);
enqueueUpdateRunningList();
}
@@ -1527,6 +1528,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();
@@ -2189,6 +2199,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 +2269,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 +2342,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/OomAdjuster.java b/services/core/java/com/android/server/am/OomAdjuster.java
index 13d367a95942..336a35e7a7e3 100644
--- a/services/core/java/com/android/server/am/OomAdjuster.java
+++ b/services/core/java/com/android/server/am/OomAdjuster.java
@@ -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) {
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..eea667ef2f39 100644
--- a/services/core/java/com/android/server/am/ProcessRecord.java
+++ b/services/core/java/com/android/server/am/ProcessRecord.java
@@ -1443,17 +1443,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 0954c49f94d6..cfd22fbdeece 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",
@@ -228,6 +234,7 @@ public class SettingsToPropertiesMapper {
"media_reliability",
"media_solutions",
"media_tv",
+ "microxr",
"nearby",
"nfc",
"pdf_viewer",
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 c8b8909f203f..f7d7ed541780 100644
--- a/services/core/java/com/android/server/appop/AppOpsService.java
+++ b/services/core/java/com/android/server/appop/AppOpsService.java
@@ -3006,7 +3006,7 @@ public class AppOpsService extends IAppOpsService.Stub {
UidState uidState = getUidStateLocked(uid, false);
if (uidState != null) {
int rawUidMode = mAppOpsCheckingService.getUidMode(
- uidState.uid, getPersistentId(virtualDeviceId), code);
+ uidState.uid, getPersistentDeviceIdForOp(virtualDeviceId, code), code);
if (rawUidMode != AppOpsManager.opToDefaultMode(code)) {
return raw ? rawUidMode :
@@ -3069,7 +3069,7 @@ public class AppOpsService extends IAppOpsService.Stub {
int switchCode = AppOpsManager.opToSwitch(code);
int rawUidMode = mAppOpsCheckingService.getUidMode(uid,
- getPersistentId(virtualDeviceId), switchCode);
+ getPersistentDeviceIdForOp(virtualDeviceId, switchCode), switchCode);
if (rawUidMode != AppOpsManager.opToDefaultMode(switchCode)) {
return raw ? rawUidMode : evaluateForegroundMode(uid, switchCode, rawUidMode);
@@ -3396,7 +3396,7 @@ public class AppOpsService extends IAppOpsService.Stub {
}
final Op op = getOpLocked(ops, code, uid, true);
final AttributedOp attributedOp = op.getOrCreateAttribution(op, attributionTag,
- getPersistentId(virtualDeviceId));
+ getPersistentDeviceIdForOp(virtualDeviceId, code));
if (attributedOp.isRunning()) {
Slog.w(TAG, "Noting op not finished: uid " + uid + " pkg " + packageName + " code "
+ code + " startTime of in progress event="
@@ -3418,15 +3418,15 @@ public class AppOpsService extends IAppOpsService.Stub {
// If there is a non-default per UID policy (we set UID op mode only if
// non-default) it takes over, otherwise use the per package policy.
- } else if (mAppOpsCheckingService.getUidMode(
- uidState.uid, getPersistentId(virtualDeviceId), switchCode)
+ } else if (mAppOpsCheckingService.getUidMode(uidState.uid,
+ getPersistentDeviceIdForOp(virtualDeviceId, switchCode), switchCode)
!= AppOpsManager.opToDefaultMode(switchCode)) {
final int uidMode =
uidState.evalMode(
code,
mAppOpsCheckingService.getUidMode(
uidState.uid,
- getPersistentId(virtualDeviceId),
+ getPersistentDeviceIdForOp(virtualDeviceId, switchCode),
switchCode));
if (uidMode != AppOpsManager.MODE_ALLOWED) {
if (DEBUG) Slog.d(TAG, "noteOperation: uid reject #" + uidMode + " for code "
@@ -3478,7 +3478,8 @@ public class AppOpsService extends IAppOpsService.Stub {
virtualDeviceId, flags, AppOpsManager.MODE_ALLOWED);
attributedOp.accessed(proxyUid, proxyPackageName, proxyAttributionTag,
- getPersistentId(proxyVirtualDeviceId), uidState.getState(), flags, notedCount);
+ getPersistentDeviceIdForOp(proxyVirtualDeviceId, code), uidState.getState(),
+ flags, notedCount);
if (shouldCollectAsyncNotedOp) {
collectAsyncNotedOp(uid, packageName, code, attributionTag, flags, message,
@@ -4045,7 +4046,7 @@ public class AppOpsService extends IAppOpsService.Stub {
}
final Op op = getOpLocked(ops, code, uid, true);
final AttributedOp attributedOp = op.getOrCreateAttribution(op, attributionTag,
- getPersistentId(virtualDeviceId));
+ getPersistentDeviceIdForOp(virtualDeviceId, code));
final UidState uidState = ops.uidState;
isRestricted = isOpRestrictedLocked(uid, code, packageName, attributionTag,
virtualDeviceId, pvr.bypass, false);
@@ -4058,8 +4059,9 @@ public class AppOpsService extends IAppOpsService.Stub {
// If there is a non-default per UID policy (we set UID op mode only if
// non-default) it takes over, otherwise use the per package policy.
} else if ((rawUidMode =
- mAppOpsCheckingService.getUidMode(
- uidState.uid, getPersistentId(virtualDeviceId), switchCode))
+ mAppOpsCheckingService.getUidMode(
+ uidState.uid, getPersistentDeviceIdForOp(virtualDeviceId, switchCode),
+ switchCode))
!= AppOpsManager.opToDefaultMode(switchCode)) {
final int uidMode = uidState.evalMode(code, rawUidMode);
if (!shouldStartForMode(uidMode, startIfModeDefault)) {
@@ -4107,11 +4109,13 @@ public class AppOpsService extends IAppOpsService.Stub {
try {
if (isRestricted) {
attributedOp.createPaused(clientId, virtualDeviceId, proxyUid, proxyPackageName,
- proxyAttributionTag, getPersistentId(proxyVirtualDeviceId),
+ proxyAttributionTag,
+ getPersistentDeviceIdForOp(proxyVirtualDeviceId, code),
uidState.getState(), flags, attributionFlags, attributionChainId);
} else {
attributedOp.started(clientId, virtualDeviceId, proxyUid, proxyPackageName,
- proxyAttributionTag, getPersistentId(proxyVirtualDeviceId),
+ proxyAttributionTag,
+ getPersistentDeviceIdForOp(proxyVirtualDeviceId, code),
uidState.getState(), flags, attributionFlags, attributionChainId);
startType = START_TYPE_STARTED;
}
@@ -4179,15 +4183,15 @@ public class AppOpsService extends IAppOpsService.Stub {
final int switchCode = AppOpsManager.opToSwitch(code);
// If there is a non-default mode per UID policy (we set UID op mode only if
// non-default) it takes over, otherwise use the per package policy.
- if (mAppOpsCheckingService.getUidMode(
- uidState.uid, getPersistentId(virtualDeviceId), switchCode)
+ if (mAppOpsCheckingService.getUidMode(uidState.uid,
+ getPersistentDeviceIdForOp(virtualDeviceId, switchCode), switchCode)
!= AppOpsManager.opToDefaultMode(switchCode)) {
final int uidMode =
uidState.evalMode(
code,
mAppOpsCheckingService.getUidMode(
uidState.uid,
- getPersistentId(virtualDeviceId),
+ getPersistentDeviceIdForOp(virtualDeviceId, switchCode),
switchCode));
if (!shouldStartForMode(uidMode, startIfModeDefault)) {
if (DEBUG) {
@@ -4350,7 +4354,8 @@ public class AppOpsService extends IAppOpsService.Stub {
return;
}
final AttributedOp attributedOp =
- op.mDeviceAttributedOps.getOrDefault(getPersistentId(virtualDeviceId),
+ op.mDeviceAttributedOps.getOrDefault(
+ getPersistentDeviceIdForOp(virtualDeviceId, code),
new ArrayMap<>()).get(attributionTag);
if (attributedOp == null) {
Slog.e(TAG, "Attribution not found: uid=" + uid + " pkg=" + packageName + "("
@@ -4641,7 +4646,8 @@ public class AppOpsService extends IAppOpsService.Stub {
return true;
}
if (mVirtualDeviceManagerInternal == null) {
- return true;
+ Slog.w(TAG, "VirtualDeviceManagerInternal is null when device Id is non-default");
+ return false;
}
if (mVirtualDeviceManagerInternal.isValidVirtualDeviceId(virtualDeviceId)) {
mKnownDeviceIds.put(virtualDeviceId,
@@ -7310,7 +7316,13 @@ public class AppOpsService extends IAppOpsService.Stub {
return packageNames;
}
- @NonNull private String getPersistentId(int virtualDeviceId) {
+ // For ops associated with device aware permissions, if virtual device id is non-default, we
+ // will return string version of that id provided the virtual device has corresponding camera or
+ // audio policy.
+ @NonNull private String getPersistentDeviceIdForOp(int virtualDeviceId, int op) {
+ virtualDeviceId = PermissionManager.resolveDeviceIdForPermissionCheck(mContext,
+ virtualDeviceId, AppOpsManager.opToPermission(op));
+
if (virtualDeviceId == Context.DEVICE_ID_DEFAULT) {
return PERSISTENT_DEVICE_ID_DEFAULT;
}
@@ -7319,6 +7331,7 @@ public class AppOpsService extends IAppOpsService.Stub {
}
String persistentId =
mVirtualDeviceManagerInternal.getPersistentIdForDevice(virtualDeviceId);
+
if (persistentId == null) {
persistentId = mKnownDeviceIds.get(virtualDeviceId);
}
diff --git a/services/core/java/com/android/server/appop/AppOpsUidStateTrackerImpl.java b/services/core/java/com/android/server/appop/AppOpsUidStateTrackerImpl.java
index ca9a25b86aed..6f8c241a86ae 100644
--- a/services/core/java/com/android/server/appop/AppOpsUidStateTrackerImpl.java
+++ b/services/core/java/com/android/server/appop/AppOpsUidStateTrackerImpl.java
@@ -341,17 +341,15 @@ class AppOpsUidStateTrackerImpl implements AppOpsUidStateTracker {
}
private void commitUidPendingState(int uid) {
- int pendingUidState = mPendingUidStates.get(uid,
- mUidStates.get(uid, MIN_PRIORITY_UID_STATE));
- int pendingCapability = mPendingCapability.get(uid,
- mCapability.get(uid, PROCESS_CAPABILITY_NONE));
- boolean pendingAppWidgetVisible = mPendingAppWidgetVisible.get(uid,
- mAppWidgetVisible.get(uid, false));
int uidState = mUidStates.get(uid, MIN_PRIORITY_UID_STATE);
int capability = mCapability.get(uid, PROCESS_CAPABILITY_NONE);
boolean appWidgetVisible = mAppWidgetVisible.get(uid, false);
+ int pendingUidState = mPendingUidStates.get(uid, uidState);
+ int pendingCapability = mPendingCapability.get(uid, capability);
+ boolean pendingAppWidgetVisible = mPendingAppWidgetVisible.get(uid, appWidgetVisible);
+
boolean foregroundChange = uidState <= UID_STATE_MAX_LAST_NON_RESTRICTED
!= pendingUidState <= UID_STATE_MAX_LAST_NON_RESTRICTED
|| capability != pendingCapability
diff --git a/services/core/java/com/android/server/appop/AttributedOp.java b/services/core/java/com/android/server/appop/AttributedOp.java
index 9dd09cef88f9..40085ed89294 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);
}
/**
@@ -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..c53e4bdc2205 100644
--- a/services/core/java/com/android/server/appop/DiscreteOpsDbHelper.java
+++ b/services/core/java/com/android/server/appop/DiscreteOpsDbHelper.java
@@ -189,11 +189,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 +249,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 +265,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 84402c85471f..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.
@@ -177,6 +165,8 @@ abstract class DiscreteOpsRegistry {
*/
abstract void writeAndClearOldAccessHistory();
+ void shutdown() {}
+
/** Remove all discrete op events. */
abstract void clearHistory();
@@ -216,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) {
@@ -225,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,
@@ -249,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) {
@@ -271,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 604cb30294a9..0e1fbf3a6d1a 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;
@@ -57,13 +56,18 @@ import java.util.Set;
public class DiscreteOpsSqlRegistry extends DiscreteOpsRegistry {
private static final String TAG = "DiscreteOpsSqlRegistry";
+ private static final long DB_WRITE_INTERVAL = Duration.ofMinutes(10).toMillis();
+ private static final long EXPIRED_ENTRY_DELETION_INTERVAL = Duration.ofHours(6).toMillis();
+
+ // Event type handled by SqliteWriteHandler
+ private static final int WRITE_DATABASE_RECURRING = 1;
+ private static final int DELETE_EXPIRED_ENTRIES = 2;
+ private static final int WRITE_DATABASE_CACHE_FULL = 3;
+
private final Context mContext;
private final DiscreteOpsDbHelper mDiscreteOpsDbHelper;
private final SqliteWriteHandler mSqliteWriteHandler;
private final DiscreteOpCache mDiscreteOpCache = new DiscreteOpCache(512);
- private static final long THREE_HOURS = Duration.ofHours(3).toMillis();
- private static final int WRITE_CACHE_EVICTED_OP_EVENTS = 1;
- private static final int DELETE_OLD_OP_EVENTS = 2;
// Attribution chain id is used to identify an attribution source chain, This is
// set for startOp only. PermissionManagerService resets this ID on device restart, so
// we use previously persisted chain id as offset, and add it to chain id received from
@@ -83,21 +87,16 @@ public class DiscreteOpsSqlRegistry extends DiscreteOpsRegistry {
mSqliteWriteHandler = new SqliteWriteHandler(thread.getLooper());
mDiscreteOpsDbHelper = new DiscreteOpsDbHelper(context, databaseFile);
mChainIdOffset = mDiscreteOpsDbHelper.getLargestAttributionChainId();
+ mSqliteWriteHandler.sendEmptyMessageDelayed(WRITE_DATABASE_RECURRING, DB_WRITE_INTERVAL);
+ mSqliteWriteHandler.sendEmptyMessageDelayed(DELETE_EXPIRED_ENTRIES,
+ EXPIRED_ENTRY_DELETION_INTERVAL);
}
@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) {
- 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;
}
@@ -117,15 +116,14 @@ public class DiscreteOpsSqlRegistry extends DiscreteOpsRegistry {
}
@Override
- void writeAndClearOldAccessHistory() {
- // Let the sql impl also follow the same disk write frequencies as xml,
- // controlled by AppOpsService.
+ void shutdown() {
+ mSqliteWriteHandler.removeAllPendingMessages();
mDiscreteOpsDbHelper.insertDiscreteOps(mDiscreteOpCache.getAllEventsAndClear());
- if (!mSqliteWriteHandler.hasMessages(DELETE_OLD_OP_EVENTS)) {
- if (mSqliteWriteHandler.sendEmptyMessageDelayed(DELETE_OLD_OP_EVENTS, THREE_HOURS)) {
- Slog.w(TAG, "DELETE_OLD_OP_EVENTS is not queued");
- }
- }
+ }
+
+ @Override
+ void writeAndClearOldAccessHistory() {
+ // no-op
}
@Override
@@ -175,14 +173,14 @@ public class DiscreteOpsSqlRegistry extends DiscreteOpsRegistry {
@Nullable String attributionTagFilter, int opFlagsFilter,
Set<String> attributionExemptPkgs) {
// flush the cache into database before read.
- writeAndClearOldAccessHistory();
+ mDiscreteOpsDbHelper.insertDiscreteOps(mDiscreteOpCache.getAllEventsAndClear());
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) {
@@ -215,14 +213,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.getAllEventsAndClear());
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: ");
@@ -363,20 +362,59 @@ public class DiscreteOpsSqlRegistry extends DiscreteOpsRegistry {
@Override
public void handleMessage(Message msg) {
switch (msg.what) {
- case WRITE_CACHE_EVICTED_OP_EVENTS:
- List<DiscreteOp> opEvents = (List<DiscreteOp>) msg.obj;
- mDiscreteOpsDbHelper.insertDiscreteOps(opEvents);
- break;
- case DELETE_OLD_OP_EVENTS:
+ case WRITE_DATABASE_RECURRING -> {
+ try {
+ List<DiscreteOp> evictedEvents;
+ synchronized (mDiscreteOpCache) {
+ evictedEvents = mDiscreteOpCache.evict();
+ }
+ mDiscreteOpsDbHelper.insertDiscreteOps(evictedEvents);
+ } finally {
+ mSqliteWriteHandler.sendEmptyMessageDelayed(WRITE_DATABASE_RECURRING,
+ DB_WRITE_INTERVAL);
+ // Schedule a cleanup to truncate older (before cutoff time) entries.
+ if (!mSqliteWriteHandler.hasMessages(DELETE_EXPIRED_ENTRIES)) {
+ mSqliteWriteHandler.sendEmptyMessageDelayed(DELETE_EXPIRED_ENTRIES,
+ EXPIRED_ENTRY_DELETION_INTERVAL);
+ }
+ }
+ }
+ case DELETE_EXPIRED_ENTRIES -> {
long cutOffTimeStamp = System.currentTimeMillis() - sDiscreteHistoryCutoff;
mDiscreteOpsDbHelper.execSQL(
DiscreteOpsTable.DELETE_TABLE_DATA_BEFORE_ACCESS_TIME,
new Object[]{cutOffTimeStamp});
- break;
- default:
- throw new IllegalStateException("Unexpected value: " + msg.what);
+ }
+ case WRITE_DATABASE_CACHE_FULL -> {
+ try {
+ List<DiscreteOp> evictedEvents;
+ synchronized (mDiscreteOpCache) {
+ evictedEvents = mDiscreteOpCache.evict();
+ // if nothing to evict, just write the whole cache to database.
+ if (evictedEvents.isEmpty()
+ && mDiscreteOpCache.size() >= mDiscreteOpCache.capacity()) {
+ evictedEvents.addAll(mDiscreteOpCache.mCache);
+ mDiscreteOpCache.clear();
+ }
+ }
+ mDiscreteOpsDbHelper.insertDiscreteOps(evictedEvents);
+ } finally {
+ // Just in case initial message is not scheduled.
+ if (!mSqliteWriteHandler.hasMessages(WRITE_DATABASE_RECURRING)) {
+ mSqliteWriteHandler.sendEmptyMessageDelayed(WRITE_DATABASE_RECURRING,
+ DB_WRITE_INTERVAL);
+ }
+ }
+ }
+ default -> throw new IllegalStateException("Unexpected value: " + msg.what);
}
}
+
+ void removeAllPendingMessages() {
+ removeMessages(WRITE_DATABASE_RECURRING);
+ removeMessages(DELETE_EXPIRED_ENTRIES);
+ removeMessages(WRITE_DATABASE_CACHE_FULL);
+ }
}
/**
@@ -390,6 +428,7 @@ public class DiscreteOpsSqlRegistry extends DiscreteOpsRegistry {
* 4) During shutdown.
*/
class DiscreteOpCache {
+ private static final String TAG = "DiscreteOpCache";
private final int mCapacity;
private final ArraySet<DiscreteOp> mCache;
@@ -404,23 +443,9 @@ public class DiscreteOpsSqlRegistry extends DiscreteOpsRegistry {
return;
}
mCache.add(opEvent);
+
if (mCache.size() >= mCapacity) {
- if (DEBUG_LOG) {
- Slog.i(TAG, "Current discrete ops cache size: " + mCache.size());
- }
- List<DiscreteOp> evictedEvents = evict();
- if (DEBUG_LOG) {
- Slog.i(TAG, "Evicted discrete ops size: " + evictedEvents.size());
- }
- // if nothing to evict, just write the whole cache to disk
- if (evictedEvents.isEmpty()) {
- Slog.w(TAG, "No discrete ops event is evicted, write cache to db.");
- evictedEvents.addAll(mCache);
- mCache.clear();
- }
- Message msg = mSqliteWriteHandler.obtainMessage(
- WRITE_CACHE_EVICTED_OP_EVENTS, evictedEvents);
- mSqliteWriteHandler.sendMessage(msg);
+ mSqliteWriteHandler.sendEmptyMessage(WRITE_DATABASE_CACHE_FULL);
}
}
}
@@ -461,6 +486,14 @@ public class DiscreteOpsSqlRegistry extends DiscreteOpsRegistry {
}
}
+ int size() {
+ return mCache.size();
+ }
+
+ int capacity() {
+ return mCapacity;
+ }
+
/**
* Remove all entries from the cache.
*/
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 928a4b270b59..06e43e8ec68d 100644
--- a/services/core/java/com/android/server/appop/HistoricalRegistry.java
+++ b/services/core/java/com/android/server/appop/HistoricalRegistry.java
@@ -497,7 +497,7 @@ final class HistoricalRegistry {
@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,7 +510,7 @@ final class HistoricalRegistry {
mDiscreteRegistry.recordDiscreteAccess(uid, packageName, deviceId, op,
attributionTag, flags, uidState, accessTime, -1, attributionFlags,
- attributionChainId, accessType);
+ attributionChainId);
}
}
}
@@ -533,8 +533,7 @@ final class HistoricalRegistry {
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,7 +545,7 @@ final class HistoricalRegistry {
attributionTag, uidState, flags, increment);
mDiscreteRegistry.recordDiscreteAccess(uid, packageName, deviceId, op,
attributionTag, flags, uidState, eventStartTime, increment,
- attributionFlags, attributionChainId, accessType);
+ attributionFlags, attributionChainId);
}
}
}
@@ -750,6 +749,7 @@ final class HistoricalRegistry {
}
// Do not call persistPendingHistory inside the memory lock, due to possible deadlock
persistPendingHistory();
+ mDiscreteRegistry.shutdown();
}
void persistPendingHistory() {
diff --git a/services/core/java/com/android/server/audio/AudioDeviceBroker.java b/services/core/java/com/android/server/audio/AudioDeviceBroker.java
index ef80d59993e9..8ef79a916530 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;
@@ -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..829d9ea7495f 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,7 +2477,6 @@ public class AudioDeviceInventory {
// always remove regardless of the result
mConnectedDevices.remove(
DeviceInfo.makeDeviceListKey(AudioSystem.DEVICE_IN_BLUETOOTH_A2DP, address));
- mDeviceBroker.postCheckCommunicationDeviceRemoval(ada);
}
@GuardedBy("mDevicesLock")
@@ -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) {
@@ -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..ada1cd73f775 100644
--- a/services/core/java/com/android/server/audio/AudioService.java
+++ b/services/core/java/com/android/server/audio/AudioService.java
@@ -7806,7 +7806,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");
diff --git a/services/core/java/com/android/server/clipboard/OWNERS b/services/core/java/com/android/server/clipboard/OWNERS
index 0d5dbf9acac3..4ca4b80ab2f0 100644
--- a/services/core/java/com/android/server/clipboard/OWNERS
+++ b/services/core/java/com/android/server/clipboard/OWNERS
@@ -1,3 +1,3 @@
-per-file EmulatorClipboardMonitor.java = bohu@google.com,lfy@google.com,rkir@google.com
+per-file EmulatorClipboardMonitor.java = bohu@google.com,rkir@google.com
olilan@google.com
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/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..7b714ad2bd9e 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<>();
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/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 bbc13cc6ae7e..2751835f9958 100644
--- a/services/core/java/com/android/server/display/mode/ModeChangeObserver.java
+++ b/services/core/java/com/android/server/display/mode/ModeChangeObserver.java
@@ -23,8 +23,6 @@ import android.view.Display;
import android.view.DisplayAddress;
import android.view.DisplayEventReceiver;
-import com.android.internal.annotations.KeepForWeakReference;
-
import java.util.HashSet;
import java.util.Set;
@@ -35,7 +33,6 @@ final class ModeChangeObserver {
private final DisplayModeDirector.Injector mInjector;
@SuppressWarnings("unused")
- @KeepForWeakReference
private DisplayEventReceiver mModeChangeListener;
private final SparseArray<Set<Integer>> mRejectedModesByDisplay = new SparseArray<>();
private Looper mLooper;
diff --git a/services/core/java/com/android/server/dreams/DreamManagerService.java b/services/core/java/com/android/server/dreams/DreamManagerService.java
index 7e8bb28b6a37..144caea05d00 100644
--- a/services/core/java/com/android/server/dreams/DreamManagerService.java
+++ b/services/core/java/com/android/server/dreams/DreamManagerService.java
@@ -1365,6 +1365,21 @@ public final class DreamManagerService extends SystemService {
}
}
+ @Override
+ public void setScreensaverEnabled(boolean enabled) {
+ checkPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS);
+ final UserHandle userHandle = getCallingUserHandle();
+ final long ident = Binder.clearCallingIdentity();
+ try {
+ Settings.Secure.putIntForUser(mContext.getContentResolver(),
+ Settings.Secure.SCREENSAVER_ENABLED, enabled ? 1 : 0,
+ userHandle.getIdentifier());
+ mPowerManagerInternal.updateSettings();
+ } finally {
+ Binder.restoreCallingIdentity(ident);
+ }
+ }
+
boolean canLaunchDreamActivity(String dreamPackageName, String packageName,
int callingUid) {
if (dreamPackageName == null || packageName == 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/InputDataStore.java b/services/core/java/com/android/server/input/InputDataStore.java
index 834f8154240e..6ed50b6fbdcb 100644
--- a/services/core/java/com/android/server/input/InputDataStore.java
+++ b/services/core/java/com/android/server/input/InputDataStore.java
@@ -89,6 +89,10 @@ public final class InputDataStore {
final InputStream inputStream = mInputGestureFileInjector.openRead(userId);
inputGestureDataList = readInputGesturesXml(inputStream, false);
inputStream.close();
+ } catch (FileNotFoundException exception) {
+ // There are valid reasons for the file to be missing, such as shortcuts having not
+ // been registered by the user.
+ return List.of();
} catch (IOException exception) {
// In case we are unable to read from the file on disk or another IO operation error,
// fail gracefully.
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/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/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..69353becc692 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);
}
@@ -466,7 +468,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 +485,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 +498,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 +516,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 +528,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 +536,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 +548,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,7 +570,8 @@ 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);
}
@@ -579,7 +585,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..68ad8f7e9433 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.
diff --git a/services/core/java/com/android/server/inputmethod/OWNERS b/services/core/java/com/android/server/inputmethod/OWNERS
index e507c6ba40a1..9d8aef943fa5 100644
--- a/services/core/java/com/android/server/inputmethod/OWNERS
+++ b/services/core/java/com/android/server/inputmethod/OWNERS
@@ -1,7 +1,6 @@
set noparent
roosa@google.com
-yukawa@google.com
tarandeep@google.com
fstern@google.com
cosminbaies@google.com
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..2d937bdcc683 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,43 @@ public class ContextHubEndpointBroker extends IContextHubEndpoint.Stub
consumer.accept(sequenceNumber);
}
}
+
+ public boolean isInMessageHistory(HubMessage message) {
+ // 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 addMessageToHistory(HubMessage message) {
+ 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 +277,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 +308,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 +335,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 +355,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 +438,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 +460,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 +474,7 @@ public class ContextHubEndpointBroker extends IContextHubEndpoint.Stub
+ ". id="
+ id
+ ", remote:"
- + mSessionInfoMap.get(id).getRemoteEndpointInfo());
+ + mSessionMap.get(id).getRemoteEndpointInfo());
sb.append(System.lineSeparator());
}
}
@@ -485,23 +530,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 +562,7 @@ public class ContextHubEndpointBroker extends IContextHubEndpoint.Stub
/* package */ boolean hasSessionId(int sessionId) {
synchronized (mOpenSessionLock) {
- return mSessionInfoMap.contains(sessionId);
+ return mSessionMap.contains(sessionId);
}
}
@@ -531,8 +576,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 +600,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 +612,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 +622,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).isInMessageHistory(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).addMessageToHistory(message);
+ }
+ return success ? ErrorCode.OK : ErrorCode.TRANSIENT_ERROR;
+ }
}
/**
@@ -634,7 +685,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 +695,7 @@ public class ContextHubEndpointBroker extends IContextHubEndpoint.Stub
mTransactionManager.onMessageDeliveryResponse(
sequenceNumber, /* success= */ false);
});
- mSessionInfoMap.remove(sessionId);
+ mSessionMap.remove(sessionId);
}
return info != null;
}
@@ -656,7 +707,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/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/locksettings/recoverablekeystore/OWNERS b/services/core/java/com/android/server/locksettings/recoverablekeystore/OWNERS
index ebf7e6bed064..e54061670346 100644
--- a/services/core/java/com/android/server/locksettings/recoverablekeystore/OWNERS
+++ b/services/core/java/com/android/server/locksettings/recoverablekeystore/OWNERS
@@ -1,3 +1,2 @@
aseemk@google.com
-bozhu@google.com
dementyev@google.com
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..9e38435ff7f1 100644
--- a/services/core/java/com/android/server/media/quality/MediaQualityService.java
+++ b/services/core/java/com/android/server/media/quality/MediaQualityService.java
@@ -48,7 +48,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;
@@ -187,7 +186,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 +220,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 +248,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 +289,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 +324,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 +332,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 +342,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,
@@ -387,7 +382,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 +401,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 +418,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 +435,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 +468,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 +497,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 +537,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 +572,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 +589,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 +629,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 +728,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 +766,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 +786,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 +819,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 +835,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 +848,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 +863,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 +874,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 +901,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 +918,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 +939,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 +956,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 +977,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 +994,7 @@ public class MediaQualityService extends SystemService {
@GuardedBy("mAmbientBacklightLock")
@Override
- public boolean isAmbientBacklightEnabled(UserHandle user) {
+ public boolean isAmbientBacklightEnabled(int userId) {
return false;
}
}
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/GroupHelper.java b/services/core/java/com/android/server/notification/GroupHelper.java
index 02f817e9cd44..3f4df1dcf4e9 100644
--- a/services/core/java/com/android/server/notification/GroupHelper.java
+++ b/services/core/java/com/android/server/notification/GroupHelper.java
@@ -35,6 +35,7 @@ import android.app.ActivityManager;
import android.app.Notification;
import android.app.NotificationChannel;
import android.app.NotificationManager;
+import android.app.PendingIntent;
import android.content.Context;
import android.content.pm.PackageManager;
import android.content.pm.PackageManager.NameNotFoundException;
@@ -130,8 +131,10 @@ public class GroupHelper {
mUngroupedAbuseNotifications = new ArrayMap<>();
// Contains the list of group summaries that were canceled when "singleton groups" were
- // force grouped. Used to remove the original group's children when an app cancels the
- // already removed summary. Key is userId|packageName|g:OriginalGroupName
+ // force grouped. Key is userId|packageName|g:OriginalGroupName. Used to:
+ // 1) remove the original group's children when an app cancels the already removed summary.
+ // 2) perform the same side effects that would happen if the group is removed because
+ // all its force-regrouped children are removed (e.g. firing its deleteIntent).
@GuardedBy("mAggregatedNotifications")
private final ArrayMap<FullyQualifiedGroupKey, CachedSummary>
mCanceledSummaries = new ArrayMap<>();
@@ -278,7 +281,11 @@ public class GroupHelper {
public void onNotificationRemoved(NotificationRecord record) {
try {
if (notificationForceGrouping()) {
- onNotificationRemoved(record, new ArrayList<>());
+ Slog.wtf(TAG,
+ "This overload of onNotificationRemoved() should not be called if "
+ + "notification_force_grouping is enabled!",
+ new Exception("call stack"));
+ onNotificationRemoved(record, new ArrayList<>(), false);
} else {
final StatusBarNotification sbn = record.getSbn();
maybeUngroup(sbn, true, sbn.getUserId());
@@ -926,10 +933,12 @@ public class GroupHelper {
*
* @param record the removed notification
* @param notificationList the full notification list from NotificationManagerService
+ * @param sendingDelete whether the removed notification is being removed in a way that sends
+ * its {@code deleteIntent}
*/
@FlaggedApi(android.service.notification.Flags.FLAG_NOTIFICATION_FORCE_GROUPING)
protected void onNotificationRemoved(final NotificationRecord record,
- final List<NotificationRecord> notificationList) {
+ final List<NotificationRecord> notificationList, boolean sendingDelete) {
final StatusBarNotification sbn = record.getSbn();
final String pkgName = sbn.getPackageName();
final int userId = record.getUserId();
@@ -973,9 +982,11 @@ public class GroupHelper {
}
// Try to cleanup cached summaries if notification was canceled (not snoozed)
+ // If the notification was cancelled by an action that fires its delete intent,
+ // also fire it for the cached summary.
if (record.isCanceled) {
maybeClearCanceledSummariesCache(pkgName, userId,
- record.getNotification().getGroup(), notificationList);
+ record.getNotification().getGroup(), notificationList, sendingDelete);
}
}
}
@@ -991,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);
}
@@ -1094,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) { }
@@ -1485,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());
}
@@ -1759,13 +1812,18 @@ public class GroupHelper {
private void cacheCanceledSummary(NotificationRecord record) {
final FullyQualifiedGroupKey groupKey = new FullyQualifiedGroupKey(record.getUserId(),
record.getSbn().getPackageName(), record.getNotification().getGroup());
- mCanceledSummaries.put(groupKey, new CachedSummary(record.getSbn().getId(),
- record.getSbn().getTag(), record.getNotification().getGroup(), record.getKey()));
+ mCanceledSummaries.put(groupKey, new CachedSummary(
+ record.getSbn().getId(),
+ record.getSbn().getTag(),
+ record.getNotification().getGroup(),
+ record.getKey(),
+ record.getNotification().deleteIntent));
}
@GuardedBy("mAggregatedNotifications")
private void maybeClearCanceledSummariesCache(String pkgName, int userId,
- String groupName, List<NotificationRecord> notificationList) {
+ String groupName, List<NotificationRecord> notificationList,
+ boolean sendSummaryDelete) {
final FullyQualifiedGroupKey findKey = new FullyQualifiedGroupKey(userId, pkgName,
groupName);
CachedSummary summary = mCanceledSummaries.get(findKey);
@@ -1786,6 +1844,9 @@ public class GroupHelper {
}
if (!stillHasChildren) {
removeCachedSummary(pkgName, userId, summary);
+ if (sendSummaryDelete && summary.deleteIntent != null) {
+ mCallback.sendAppProvidedSummaryDeleteIntent(pkgName, summary.deleteIntent);
+ }
}
}
}
@@ -1876,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;
@@ -1965,7 +2032,8 @@ public class GroupHelper {
}
}
- record CachedSummary(int id, String tag, String originalGroupKey, String key) {}
+ record CachedSummary(int id, String tag, String originalGroupKey, String key,
+ @Nullable PendingIntent deleteIntent) { }
protected static class NotificationAttributes {
public final int flags;
@@ -2035,6 +2103,15 @@ public class GroupHelper {
// New callbacks for API abuse grouping
void removeAppProvidedSummary(String key);
+ /**
+ * Send a cached summary's deleteIntent, when the last of its original children is removed.
+ *
+ * <p>While technically the group summary was "canceled" much earlier (because it was the
+ * summary of a sparse group and its children got reparented), the posting package expected
+ * the summary's deleteIntent to fire when the summary is auto-dismissed.
+ */
+ void sendAppProvidedSummaryDeleteIntent(String pkg, PendingIntent deleteIntent);
+
void removeNotificationFromCanceledGroup(int userId, String pkg, String groupKey,
int cancelReason);
diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java
index 6fddfb5f90a7..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,
@@ -3211,6 +3211,11 @@ public class NotificationManagerService extends SystemService {
}
@Override
+ public void sendAppProvidedSummaryDeleteIntent(String pkg, PendingIntent deleteIntent) {
+ sendDeleteIntent(deleteIntent, pkg);
+ }
+
+ @Override
public void removeNotificationFromCanceledGroup(int userId, String pkg,
String groupKey, int cancelReason) {
synchronized (mNotificationLock) {
@@ -7205,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)));
+ });
}
}
@@ -7440,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);
@@ -9898,7 +9910,8 @@ public class NotificationManagerService extends SystemService {
if (notificationForceGrouping()) {
mHandler.post(() -> {
synchronized (mNotificationLock) {
- mGroupHelper.onNotificationRemoved(r, mNotificationList);
+ mGroupHelper.onNotificationRemoved(r, mNotificationList,
+ /* sendingDelete= */ false);
}
});
} else {
@@ -10185,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) -> {
@@ -10826,20 +10845,7 @@ public class NotificationManagerService extends SystemService {
// tell the app
if (sendDelete) {
- final PendingIntent deleteIntent = r.getNotification().deleteIntent;
- if (deleteIntent != null) {
- try {
- // make sure deleteIntent cannot be used to start activities from background
- LocalServices.getService(ActivityManagerInternal.class)
- .clearPendingIntentAllowBgActivityStarts(deleteIntent.getTarget(),
- ALLOWLIST_TOKEN);
- deleteIntent.send();
- } catch (PendingIntent.CanceledException ex) {
- // do nothing - there's no relevant way to recover, and
- // no reason to let this propagate
- Slog.w(TAG, "canceled PendingIntent for " + r.getSbn().getPackageName(), ex);
- }
- }
+ sendDeleteIntent(r.getNotification().deleteIntent, r.getSbn().getPackageName());
}
// Only cancel these if this notification actually got to be posted.
@@ -10854,7 +10860,7 @@ public class NotificationManagerService extends SystemService {
mHandler.removeCallbacksAndEqualMessages(r.getKey());
mHandler.post(() -> {
synchronized (NotificationManagerService.this.mNotificationLock) {
- mGroupHelper.onNotificationRemoved(r, mNotificationList);
+ mGroupHelper.onNotificationRemoved(r, mNotificationList, sendDelete);
}
});
@@ -10952,6 +10958,21 @@ public class NotificationManagerService extends SystemService {
}
}
+ private static void sendDeleteIntent(@Nullable PendingIntent deleteIntent, String fromPkg) {
+ if (deleteIntent != null) {
+ try {
+ // make sure deleteIntent cannot be used to start activities from background
+ LocalServices.getService(ActivityManagerInternal.class)
+ .clearPendingIntentAllowBgActivityStarts(deleteIntent.getTarget(),
+ ALLOWLIST_TOKEN);
+ deleteIntent.send();
+ } catch (PendingIntent.CanceledException ex) {
+ // There's no relevant way to recover, and no reason to let this propagate
+ Slog.w(TAG, "canceled PendingIntent for " + fromPkg, ex);
+ }
+ }
+ }
+
@VisibleForTesting
void updateUriPermissions(@Nullable NotificationRecord newRecord,
@Nullable NotificationRecord oldRecord, String targetPkg, int targetUserId) {
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 0fc182f3f1bb..b26b4571b07a 100644
--- a/services/core/java/com/android/server/notification/PreferencesHelper.java
+++ b/services/core/java/com/android/server/notification/PreferencesHelper.java
@@ -42,6 +42,7 @@ import static com.android.internal.util.FrameworkStatsLog.PACKAGE_NOTIFICATION_P
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.PreferencesHelper.LockableAppFields.USER_LOCKED_BUBBLE;
import static com.android.server.notification.PreferencesHelper.LockableAppFields.USER_LOCKED_PROMOTABLE;
import android.annotation.FlaggedApi;
@@ -99,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;
@@ -155,6 +155,7 @@ public class PreferencesHelper implements RankingConfig {
private static final String ATT_VERSION = "version";
private static final String ATT_NAME = "name";
private static final String ATT_UID = "uid";
+ private static final String ATT_LAST_BUBBLES_VERSION_UPGRADE = "last_bubbles_version_upgrade";
private static final String ATT_USERID = "userid";
private static final String ATT_ID = "id";
@@ -226,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;
@@ -245,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;
@@ -256,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);
@@ -286,7 +284,8 @@ public class PreferencesHelper implements RankingConfig {
if (!TAG_RANKING.equals(tag)) return;
final int xmlVersion = parser.getAttributeInt(null, ATT_VERSION, -1);
- boolean upgradeForBubbles = xmlVersion == XML_VERSION_BUBBLES_UPGRADE;
+ boolean upgradeForBubbles = parser.getAttributeInt(null,
+ ATT_LAST_BUBBLES_VERSION_UPGRADE, -1) < Build.VERSION.SDK_INT;
boolean migrateToPermission = (xmlVersion < XML_VERSION_NOTIF_PERMISSION);
if (mShowReviewPermissionsNotification
&& (xmlVersion < XML_VERSION_REVIEW_PERMISSIONS_NOTIFICATION)) {
@@ -337,15 +336,19 @@ public class PreferencesHelper implements RankingConfig {
}
boolean skipWarningLogged = false;
boolean skipGroupWarningLogged = false;
- boolean hasSAWPermission = false;
- if (upgradeForBubbles && uid != UNKNOWN_UID) {
- hasSAWPermission = mAppOps.noteOpNoThrow(
- OP_SYSTEM_ALERT_WINDOW, uid, name, null,
- "check-notif-bubble") == AppOpsManager.MODE_ALLOWED;
- }
- int bubblePref = hasSAWPermission
- ? BUBBLE_PREFERENCE_ALL
- : parser.getAttributeInt(null, ATT_ALLOW_BUBBLE, DEFAULT_BUBBLE_PREFERENCE);
+ int bubblePref = parser.getAttributeInt(null, ATT_ALLOW_BUBBLE,
+ DEFAULT_BUBBLE_PREFERENCE);
+ boolean bubbleLocked = (parser.getAttributeInt(null,
+ ATT_APP_USER_LOCKED_FIELDS, DEFAULT_LOCKED_APP_FIELDS) & USER_LOCKED_BUBBLE)
+ != 0;
+ if (!bubbleLocked
+ && upgradeForBubbles
+ && uid != UNKNOWN_UID
+ && mAppOps.noteOpNoThrow(OP_SYSTEM_ALERT_WINDOW, uid, name, null,
+ "check-notif-bubble") == AppOpsManager.MODE_ALLOWED) {
+ // User hasn't changed bubble pref & the app has SAW, so allow all bubbles.
+ bubblePref = BUBBLE_PREFERENCE_ALL;
+ }
int appImportance = parser.getAttributeInt(null, ATT_IMPORTANCE, DEFAULT_IMPORTANCE);
// when data is loaded from disk it's loaded as USER_ALL, but restored data that
@@ -684,6 +687,7 @@ public class PreferencesHelper implements RankingConfig {
public void writeXml(TypedXmlSerializer out, boolean forBackup, int userId) throws IOException {
out.startTag(null, TAG_RANKING);
out.attributeInt(null, ATT_VERSION, XML_VERSION);
+ out.attributeInt(null, ATT_LAST_BUBBLES_VERSION_UPGRADE, Build.VERSION.SDK_INT);
if (mHideSilentStatusBarIcons != DEFAULT_HIDE_SILENT_STATUS_BAR_ICONS) {
out.startTag(null, TAG_STATUS_ICONS);
out.attributeBoolean(null, ATT_HIDE_SILENT, mHideSilentStatusBarIcons);
@@ -1187,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/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/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 463989adc98f..f3797614dee0 100644
--- a/services/core/java/com/android/server/pm/BackgroundInstallControlService.java
+++ b/services/core/java/com/android/server/pm/BackgroundInstallControlService.java
@@ -390,10 +390,11 @@ public class BackgroundInstallControlService extends SystemService {
.max(Comparator.comparingLong(PackageInstaller.SessionInfo::getCreatedMillis));
}
- // ADB sets installerPackageName to null, this creates a loophole to bypass BIC which will be
- // addressed with b/265203007
private boolean installedByAdb(String initiatingPackageName) {
- if(PackageManagerServiceUtils.isInstalledByAdb(initiatingPackageName)) {
+ // GTS tests needs to adopt shell identity to install apps.
+ 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");
return true;
}
diff --git a/services/core/java/com/android/server/pm/InstallPackageHelper.java b/services/core/java/com/android/server/pm/InstallPackageHelper.java
index f96846cdb2f4..acdc79fb9922 100644
--- a/services/core/java/com/android/server/pm/InstallPackageHelper.java
+++ b/services/core/java/com/android/server/pm/InstallPackageHelper.java
@@ -1067,6 +1067,9 @@ final class InstallPackageHelper {
void doPostDexopt(List<ReconciledPackage> reconciledPackages,
List<InstallRequest> requests, Map<String, Boolean> createdAppId,
MoveInfo moveInfo, long acquireTime) {
+ for (InstallRequest request : requests) {
+ request.onWaitDexoptFinished();
+ }
boolean success = false;
try {
if (commitInstallPackages(reconciledPackages)) {
@@ -1218,6 +1221,7 @@ final class InstallPackageHelper {
CompletableFuture<Void> future =
DexOptHelper.performDexoptIfNeededAsync(request, mDexManager);
completableFutures.add(future);
+ request.onWaitDexoptStarted();
}
if (!completableFutures.isEmpty()) {
diff --git a/services/core/java/com/android/server/pm/InstallRequest.java b/services/core/java/com/android/server/pm/InstallRequest.java
index fbf5db5d9635..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;
@@ -1028,6 +1014,18 @@ final class InstallRequest {
}
}
+ public void onWaitDexoptStarted() {
+ if (mPackageMetrics != null) {
+ mPackageMetrics.onStepStarted(PackageMetrics.STEP_WAIT_DEXOPT);
+ }
+ }
+
+ public void onWaitDexoptFinished() {
+ if (mPackageMetrics != null) {
+ mPackageMetrics.onStepFinished(PackageMetrics.STEP_WAIT_DEXOPT);
+ }
+ }
+
public void onDexoptFinished(DexoptResult dexoptResult) {
// Only report external profile warnings when installing from adb. The goal is to warn app
// developers if they have provided bad external profiles, so it's not beneficial to report
diff --git a/services/core/java/com/android/server/pm/PackageInstallerSession.java b/services/core/java/com/android/server/pm/PackageInstallerSession.java
index 136cb1259113..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() {
@@ -5189,7 +5233,9 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub {
"Session " + sessionId + " is a parent of multi-package session and "
+ "requestUserPreapproval on the parent session isn't supported.");
}
-
+ if (statusReceiver == null) {
+ throw new IllegalArgumentException("Status receiver cannot be null.");
+ }
synchronized (mLock) {
assertPreparedAndNotSealedLocked("request of session " + sessionId);
mPreapprovalDetails = details;
@@ -5542,6 +5588,10 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub {
*/
private static void sendOnUserActionRequired(Context context, IntentSender target,
int sessionId, Intent intent) {
+ if (target == null) {
+ Slog.e(TAG, "Missing receiver for pending user action.");
+ return;
+ }
final Intent fillIn = new Intent();
fillIn.putExtra(PackageInstaller.EXTRA_SESSION_ID, sessionId);
fillIn.putExtra(PackageInstaller.EXTRA_STATUS, PackageInstaller.STATUS_PENDING_USER_ACTION);
diff --git a/services/core/java/com/android/server/pm/PackageMetrics.java b/services/core/java/com/android/server/pm/PackageMetrics.java
index 994ee421790c..22da3b2e7636 100644
--- a/services/core/java/com/android/server/pm/PackageMetrics.java
+++ b/services/core/java/com/android/server/pm/PackageMetrics.java
@@ -73,6 +73,7 @@ final class PackageMetrics {
public static final int STEP_DEXOPT = 5;
public static final int STEP_FREEZE_INSTALL = 6;
public static final int STEP_RESTORE = 7;
+ public static final int STEP_WAIT_DEXOPT = 8;
@IntDef(prefix = {"STEP_"}, value = {
STEP_PREPARE,
@@ -81,7 +82,8 @@ final class PackageMetrics {
STEP_COMMIT,
STEP_DEXOPT,
STEP_FREEZE_INSTALL,
- STEP_RESTORE
+ STEP_RESTORE,
+ STEP_WAIT_DEXOPT
})
@Retention(RetentionPolicy.SOURCE)
public @interface StepInt {
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/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..46dc75817a36 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,
@@ -6706,12 +6706,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/PowerManagerService.java b/services/core/java/com/android/server/power/PowerManagerService.java
index e3eced252d1f..dd454cd61b2c 100644
--- a/services/core/java/com/android/server/power/PowerManagerService.java
+++ b/services/core/java/com/android/server/power/PowerManagerService.java
@@ -7445,6 +7445,13 @@ public final class PowerManagerService extends SystemService
public void setDevicePostured(boolean isPostured) {
setDevicePosturedInternal(isPostured);
}
+
+ @Override
+ public void updateSettings() {
+ synchronized (mLock) {
+ updateSettingsLocked();
+ }
+ }
}
/**
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/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/advancedprotection/OWNERS b/services/core/java/com/android/server/security/advancedprotection/OWNERS
index 9bf5e58c01a9..8336ee8702b1 100644
--- a/services/core/java/com/android/server/security/advancedprotection/OWNERS
+++ b/services/core/java/com/android/server/security/advancedprotection/OWNERS
@@ -1 +1,2 @@
file:platform/frameworks/base:main:/core/java/android/security/advancedprotection/OWNERS
+per-file features/UsbDataAdvancedProtectionHook.java = georgechan@google.com, maunik@google.com
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/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..6e640d890fb8 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;
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 12f553426c80..1299a4d86623 100644
--- a/services/core/java/com/android/server/wm/AccessibilityController.java
+++ b/services/core/java/com/android/server/wm/AccessibilityController.java
@@ -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",
@@ -712,13 +671,17 @@ final class AccessibilityController {
if (!isMagnifierActivated) {
return;
}
- // All opening/closing situations.
+ // All opening/closing/recents transitions
+ boolean notify = (flags & TRANSIT_FLAG_IS_RECENTS) != 0;
switch (type) {
case WindowManager.TRANSIT_OPEN:
case WindowManager.TRANSIT_TO_FRONT:
case WindowManager.TRANSIT_CLOSE:
case WindowManager.TRANSIT_TO_BACK:
- mUserContextChangedNotifier.onWMTransition(type, flags);
+ notify = true;
+ }
+ if (notify) {
+ mUserContextChangedNotifier.onWMTransition(type, flags);
}
}
@@ -730,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();
@@ -1088,8 +1051,7 @@ final class AccessibilityController {
// causing the notifying, or the recents/home window is removed, then we won't need the
// delayed notification anymore.
void onWMTransition(@TransitionType int type, @TransitionFlags int flags) {
- if (type == WindowManager.TRANSIT_TO_FRONT
- && (flags & TRANSIT_FLAG_IS_RECENTS) != 0) {
+ if ((flags & TRANSIT_FLAG_IS_RECENTS) != 0) {
// Delay the recents to front transition notification then send after if needed.
mHasDelayedNotificationForRecentsToFrontTransition = true;
} else {
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..e91d88901751 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;
@@ -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,18 +5590,6 @@ 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();
- }
-
/** Updates draw state and shows drawn windows. */
void commitFinishDrawing(SurfaceControl.Transaction t) {
boolean committed = false;
@@ -6044,11 +6042,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 +6056,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 +6343,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 +7259,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 +7276,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 +7288,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 +7341,6 @@ final class ActivityRecord extends WindowToken {
}
mNeedsAnimationBoundsLayer = false;
- if (mNeedsLetterboxedAnimation) {
- mNeedsLetterboxedAnimation = false;
- updateLetterboxSurfaceIfNeeded(findMainWindow(), t);
- }
}
@Override
@@ -8711,9 +8693,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 +8910,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 +8967,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 +9018,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/ActivityStarter.java b/services/core/java/com/android/server/wm/ActivityStarter.java
index 247264f049d6..233f91385ca4 100644
--- a/services/core/java/com/android/server/wm/ActivityStarter.java
+++ b/services/core/java/com/android/server/wm/ActivityStarter.java
@@ -58,9 +58,8 @@ import static android.os.Process.INVALID_UID;
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_NONE;
+import static android.view.WindowManager.TRANSIT_FLAG_AVOID_MOVE_TO_FRONT;
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;
@@ -1841,6 +1840,9 @@ class ActivityStarter {
// no-user-leaving implies not entering PiP.
transition.setCanPipOnFinish(false /* canPipOnFinish */);
}
+ if (avoidMoveToFront() && transition != null) {
+ transition.addFlag(TRANSIT_FLAG_AVOID_MOVE_TO_FRONT);
+ }
if (isIndependentLaunch && transition != null) {
transitionController.requestStartTransition(transition,
mTargetTask == null ? started.getTask() : mTargetTask,
@@ -1988,6 +1990,11 @@ class ActivityStarter {
}
}
+ if (com.android.window.flags.Flags.earlyLaunchHint()) {
+ mRootWindowContainer.startPowerModeLaunchIfNeeded(
+ false /* forceSend */, mStartActivity);
+ }
+
if (mTargetRootTask == null) {
mTargetRootTask = getOrCreateRootTask(mStartActivity, mLaunchFlags, targetTask,
mOptions);
@@ -2060,8 +2067,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,
@@ -2547,11 +2556,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);
diff --git a/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java b/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java
index c45f7e8a7da2..b0563128870a 100644
--- a/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java
+++ b/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java
@@ -125,6 +125,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 +164,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 +1045,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
@@ -2983,17 +2990,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/AppCompatController.java b/services/core/java/com/android/server/wm/AppCompatController.java
index 48f08e945a59..c479591a5e0d 100644
--- a/services/core/java/com/android/server/wm/AppCompatController.java
+++ b/services/core/java/com/android/server/wm/AppCompatController.java
@@ -46,6 +46,8 @@ class AppCompatController {
private final AppCompatSizeCompatModePolicy mSizeCompatModePolicy;
@NonNull
private final AppCompatSandboxingPolicy mSandboxingPolicy;
+ @NonNull
+ private final AppCompatDisplayCompatModePolicy mDisplayCompatModePolicy;
AppCompatController(@NonNull WindowManagerService wmService,
@NonNull ActivityRecord activityRecord) {
@@ -69,6 +71,7 @@ class AppCompatController {
mSizeCompatModePolicy = new AppCompatSizeCompatModePolicy(activityRecord,
mAppCompatOverrides);
mSandboxingPolicy = new AppCompatSandboxingPolicy(activityRecord);
+ mDisplayCompatModePolicy = new AppCompatDisplayCompatModePolicy();
}
@NonNull
@@ -151,6 +154,11 @@ 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);
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/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..b91a12598e01 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(
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 f51e60c101e4..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;
@@ -312,7 +312,6 @@ public class BackgroundActivityStartController {
private final @ActivityManager.ProcessState int mCallingUidProcState;
private final boolean mIsCallingUidPersistentSystemProcess;
final BackgroundStartPrivileges mBalAllowedByPiSender;
- final BackgroundStartPrivileges mBalAllowedByPiCreatorWithHardening;
final BackgroundStartPrivileges mBalAllowedByPiCreator;
private final String mRealCallingPackage;
private final int mRealCallingUid;
@@ -379,22 +378,14 @@ public class BackgroundActivityStartController {
if (mAutoOptInCaller) {
// grant BAL privileges unless explicitly opted out
- mBalAllowedByPiCreatorWithHardening = mBalAllowedByPiCreator =
+ mBalAllowedByPiCreator =
callerBackgroundActivityStartMode == MODE_BACKGROUND_ACTIVITY_START_DENIED
? BackgroundStartPrivileges.NONE
: BackgroundStartPrivileges.ALLOW_BAL;
} else {
// for PendingIntents we restrict BAL based on target_sdk
- mBalAllowedByPiCreatorWithHardening = getBackgroundStartPrivilegesAllowedByCreator(
+ mBalAllowedByPiCreator = getBackgroundStartPrivilegesAllowedByCreator(
callingUid, callingPackage, checkedOptions);
- final BackgroundStartPrivileges mBalAllowedByPiCreatorWithoutHardening =
- callerBackgroundActivityStartMode
- == MODE_BACKGROUND_ACTIVITY_START_DENIED
- ? BackgroundStartPrivileges.NONE
- : BackgroundStartPrivileges.ALLOW_BAL;
- mBalAllowedByPiCreator = balRequireOptInByPendingIntentCreator()
- ? mBalAllowedByPiCreatorWithHardening
- : mBalAllowedByPiCreatorWithoutHardening;
}
if (mAutoOptInReason != null) {
@@ -585,9 +576,8 @@ public class BackgroundActivityStartController {
if (mCallerApp != null) {
sb.append("; inVisibleTask: ").append(mCallerApp.hasActivityInVisibleTask());
}
- sb.append("; balAllowedByPiCreator: ").append(mBalAllowedByPiCreator);
- sb.append("; balAllowedByPiCreatorWithHardening: ")
- .append(mBalAllowedByPiCreatorWithHardening);
+ sb.append("; balAllowedByPiCreator: ")
+ .append(mBalAllowedByPiCreator);
if (mResultForCaller != null) {
sb.append("; resultIfPiCreatorAllowsBal: ")
.append(balCodeToString(mResultForCaller.mCode));
@@ -638,14 +628,13 @@ public class BackgroundActivityStartController {
}
static class BalVerdict {
- static final BalVerdict BLOCK = new BalVerdict(BAL_BLOCK, false, "Blocked");
+ static final BalVerdict BLOCK = new BalVerdict(BAL_BLOCK, "Blocked");
static final BalVerdict ALLOW_BY_DEFAULT =
- new BalVerdict(BAL_ALLOW_DEFAULT, false, "Default");
+ new BalVerdict(BAL_ALLOW_DEFAULT, "Default");
// Careful using this - it will bypass all ASM checks.
static final BalVerdict ALLOW_PRIVILEGED =
- new BalVerdict(BAL_ALLOW_ALLOWLISTED_UID, false, "PRIVILEGED");
+ new BalVerdict(BAL_ALLOW_ALLOWLISTED_UID, "PRIVILEGED");
private final @BalCode int mCode;
- private final boolean mBackground;
private final String mMessage;
private String mProcessInfo;
// indicates BAL would be blocked because only creator of the PI has the privilege to allow
@@ -654,8 +643,7 @@ public class BackgroundActivityStartController {
/** indicates that this verdict is based on the real calling UID and not the calling UID */
private boolean mBasedOnRealCaller;
- BalVerdict(@BalCode int balCode, boolean background, String message) {
- this.mBackground = background;
+ BalVerdict(@BalCode int balCode, String message) {
this.mCode = balCode;
this.mMessage = message;
}
@@ -708,16 +696,7 @@ public class BackgroundActivityStartController {
builder.append(" [realCaller]");
}
if (DEBUG_ACTIVITY_STARTS) {
- builder.append(" (");
- if (mBackground) {
- builder.append("Background ");
- }
- builder.append("Activity start ");
- if (mCode == BAL_BLOCK) {
- builder.append("denied");
- } else {
- builder.append("allowed: ").append(mMessage);
- }
+ builder.append(" (").append(mMessage);
if (mProcessInfo != null) {
builder.append(" ");
builder.append(mProcessInfo);
@@ -795,7 +774,6 @@ public class BackgroundActivityStartController {
// to realCallingUid when calculating resultForRealCaller below.
if (getService().hasActiveVisibleWindow(realCallingSdkSandboxUidToAppUid)) {
state.setResultForRealCaller(new BalVerdict(BAL_ALLOW_SDK_SANDBOX,
- /*background*/ false,
"uid in SDK sandbox has visible (non-toast) window"));
return allowBasedOnRealCaller(state);
}
@@ -1059,8 +1037,7 @@ public class BackgroundActivityStartController {
|| state.mAppSwitchState == APP_SWITCH_FG_ONLY
|| isHomeApp(state.mCallingUid, state.mCallingPackage);
if (appSwitchAllowedOrFg && state.mCallingUidHasVisibleActivity) {
- return new BalVerdict(BAL_ALLOW_VISIBLE_WINDOW,
- /*background*/ false, "callingUid has visible window");
+ return new BalVerdict(BAL_ALLOW_VISIBLE_WINDOW, "callingUid has visible window");
}
return BalVerdict.BLOCK;
};
@@ -1068,7 +1045,7 @@ public class BackgroundActivityStartController {
private final BalExemptionCheck mCheckCallerNonAppVisible = state -> {
if (state.mCallingUidHasNonAppVisibleWindow) {
return new BalVerdict(BAL_ALLOW_NON_APP_VISIBLE_WINDOW,
- /*background*/ false, "callingUid has non-app visible window "
+ "callingUid has non-app visible window "
+ getService().mActiveUids.getNonAppVisibleWindowDetails(state.mCallingUid));
}
return BalVerdict.BLOCK;
@@ -1080,9 +1057,7 @@ public class BackgroundActivityStartController {
if (state.mCallingUid == Process.ROOT_UID
|| callingAppId == Process.SYSTEM_UID
|| callingAppId == Process.NFC_UID) {
- return new BalVerdict(
- BAL_ALLOW_ALLOWLISTED_UID, /*background*/ false,
- "Important callingUid");
+ return new BalVerdict(BAL_ALLOW_ALLOWLISTED_UID, "Important callingUid");
}
return BalVerdict.BLOCK;
};
@@ -1090,9 +1065,7 @@ public class BackgroundActivityStartController {
private final BalExemptionCheck mCheckCallerIsAllowlistedComponent = state -> {
// Always allow home application to start activities.
if (isHomeApp(state.mCallingUid, state.mCallingPackage)) {
- return new BalVerdict(BAL_ALLOW_ALLOWLISTED_COMPONENT,
- /*background*/ false,
- "Home app");
+ return new BalVerdict(BAL_ALLOW_ALLOWLISTED_COMPONENT, "Home app");
}
final int callingAppId = UserHandle.getAppId(state.mCallingUid);
@@ -1100,37 +1073,31 @@ public class BackgroundActivityStartController {
final WindowState imeWindow =
getService().mRootWindowContainer.getCurrentInputMethodWindow();
if (imeWindow != null && callingAppId == imeWindow.mOwnerUid) {
- return new BalVerdict(BAL_ALLOW_ALLOWLISTED_COMPONENT,
- /*background*/ false,
- "Active ime");
+ return new BalVerdict(BAL_ALLOW_ALLOWLISTED_COMPONENT, "Active ime");
}
// don't abort if the callingUid is a persistent system process
if (state.mIsCallingUidPersistentSystemProcess) {
return new BalVerdict(BAL_ALLOW_ALLOWLISTED_COMPONENT,
- /*background*/ false, "callingUid is persistent system process");
+ "callingUid is persistent system process");
}
// don't abort if the caller has the same uid as the recents component
if (getSupervisor().mRecentTasks.isCallerRecents(state.mCallingUid)) {
- return new BalVerdict(BAL_ALLOW_ALLOWLISTED_COMPONENT,
- /*background*/ true, "Recents Component");
+ return new BalVerdict(BAL_ALLOW_ALLOWLISTED_COMPONENT, "Recents Component");
}
// don't abort if the callingUid is the device owner
if (getService().isDeviceOwner(state.mCallingUid)) {
- return new BalVerdict(BAL_ALLOW_ALLOWLISTED_COMPONENT,
- /*background*/ true, "Device Owner");
+ return new BalVerdict(BAL_ALLOW_ALLOWLISTED_COMPONENT, "Device Owner");
}
// don't abort if the callingUid is a affiliated profile owner
if (getService().isAffiliatedProfileOwner(state.mCallingUid)) {
- return new BalVerdict(BAL_ALLOW_ALLOWLISTED_COMPONENT,
- /*background*/ true, "Affiliated Profile Owner");
+ return new BalVerdict(BAL_ALLOW_ALLOWLISTED_COMPONENT, "Affiliated Profile Owner");
}
// don't abort if the callingUid has companion device
final int callingUserId = UserHandle.getUserId(state.mCallingUid);
if (getService().isAssociatedCompanionApp(callingUserId, state.mCallingUid)) {
- return new BalVerdict(BAL_ALLOW_ALLOWLISTED_COMPONENT,
- /*background*/ true, "Companion App");
+ return new BalVerdict(BAL_ALLOW_ALLOWLISTED_COMPONENT, "Companion App");
}
return BalVerdict.BLOCK;
};
@@ -1139,7 +1106,6 @@ public class BackgroundActivityStartController {
// don't abort if the callingUid has START_ACTIVITIES_FROM_BACKGROUND permission
if (hasBalPermission(state.mCallingUid, state.mCallingPid)) {
return new BalVerdict(BAL_ALLOW_PERMISSION,
- /*background*/ true,
"START_ACTIVITIES_FROM_BACKGROUND permission granted");
}
return BalVerdict.BLOCK;
@@ -1149,7 +1115,7 @@ public class BackgroundActivityStartController {
if (getService().hasSystemAlertWindowPermission(state.mCallingUid, state.mCallingPid,
state.mCallingPackage)) {
return new BalVerdict(BAL_ALLOW_SAW_PERMISSION,
- /*background*/ true, "SYSTEM_ALERT_WINDOW permission is granted");
+ "SYSTEM_ALERT_WINDOW permission is granted");
}
return BalVerdict.BLOCK;
};
@@ -1159,7 +1125,7 @@ public class BackgroundActivityStartController {
if (isSystemExemptFlagEnabled() && getService().getAppOpsManager().checkOpNoThrow(
AppOpsManager.OP_SYSTEM_EXEMPT_FROM_ACTIVITY_BG_START_RESTRICTION,
state.mCallingUid, state.mCallingPackage) == AppOpsManager.MODE_ALLOWED) {
- return new BalVerdict(BAL_ALLOW_PERMISSION, /*background*/ true,
+ return new BalVerdict(BAL_ALLOW_PERMISSION,
"OP_SYSTEM_EXEMPT_FROM_ACTIVITY_BG_START_RESTRICTION appop is granted");
}
return BalVerdict.BLOCK;
@@ -1200,8 +1166,7 @@ public class BackgroundActivityStartController {
|| state.mAppSwitchState == APP_SWITCH_FG_ONLY
|| isHomeApp(state.mRealCallingUid, state.mRealCallingPackage);
if (appSwitchAllowedOrFg && state.mRealCallingUidHasVisibleActivity) {
- return new BalVerdict(BAL_ALLOW_VISIBLE_WINDOW,
- /*background*/ false, "realCallingUid has visible window");
+ return new BalVerdict(BAL_ALLOW_VISIBLE_WINDOW, "realCallingUid has visible window");
}
return BalVerdict.BLOCK;
};
@@ -1209,9 +1174,9 @@ public class BackgroundActivityStartController {
private final BalExemptionCheck mCheckRealCallerNonAppVisible = state -> {
if (state.mRealCallingUidHasNonAppVisibleWindow) {
return new BalVerdict(BAL_ALLOW_NON_APP_VISIBLE_WINDOW,
- /*background*/ false, "realCallingUid has non-app visible window "
- + getService().mActiveUids.getNonAppVisibleWindowDetails(
- state.mRealCallingUid));
+ "realCallingUid has non-app visible window "
+ + getService().mActiveUids.getNonAppVisibleWindowDetails(
+ state.mRealCallingUid));
}
return BalVerdict.BLOCK;
};
@@ -1230,9 +1195,7 @@ public class BackgroundActivityStartController {
== MODE_BACKGROUND_ACTIVITY_START_ALLOW_ALWAYS;
if (allowAlways
&& hasBalPermission(state.mRealCallingUid, state.mRealCallingPid)) {
- return new BalVerdict(BAL_ALLOW_PERMISSION,
- /*background*/ false,
- "realCallingUid has BAL permission.");
+ return new BalVerdict(BAL_ALLOW_PERMISSION, "realCallingUid has BAL permission.");
}
return BalVerdict.BLOCK;
};
@@ -1245,7 +1208,7 @@ public class BackgroundActivityStartController {
&& getService().hasSystemAlertWindowPermission(state.mRealCallingUid,
state.mRealCallingPid, state.mRealCallingPackage)) {
return new BalVerdict(BAL_ALLOW_SAW_PERMISSION,
- /*background*/ true, "SYSTEM_ALERT_WINDOW permission is granted");
+ "SYSTEM_ALERT_WINDOW permission is granted");
}
return BalVerdict.BLOCK;
};
@@ -1258,7 +1221,6 @@ public class BackgroundActivityStartController {
if ((allowAlways || state.mAllowBalExemptionForSystemProcess)
&& state.mIsRealCallingUidPersistentSystemProcess) {
return new BalVerdict(BAL_ALLOW_ALLOWLISTED_UID,
- /*background*/ false,
"realCallingUid is persistent system process AND intent "
+ "sender forced to allow.");
}
@@ -1270,7 +1232,6 @@ public class BackgroundActivityStartController {
if (getService().isAssociatedCompanionApp(
UserHandle.getUserId(state.mRealCallingUid), state.mRealCallingUid)) {
return new BalVerdict(BAL_ALLOW_ALLOWLISTED_COMPONENT,
- /*background*/ false,
"realCallingUid is a companion app.");
}
return BalVerdict.BLOCK;
@@ -1725,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/BackgroundLaunchProcessController.java b/services/core/java/com/android/server/wm/BackgroundLaunchProcessController.java
index ccf1aedb3177..31b239421baf 100644
--- a/services/core/java/com/android/server/wm/BackgroundLaunchProcessController.java
+++ b/services/core/java/com/android/server/wm/BackgroundLaunchProcessController.java
@@ -125,27 +125,27 @@ class BackgroundLaunchProcessController {
long lastActivityFinishTime) {
// Allow if the proc is instrumenting with background activity starts privs.
if (checkConfiguration.checkOtherExemptions && hasBackgroundActivityStartPrivileges) {
- return new BalVerdict(BAL_ALLOW_PERMISSION, /*background*/ true,
+ return new BalVerdict(BAL_ALLOW_PERMISSION, /*background*/
"process instrumenting with background activity starts privileges");
}
// Allow if the flag was explicitly set.
if (checkConfiguration.checkOtherExemptions && isBackgroundStartAllowedByToken(uid,
packageName, checkConfiguration.isCheckingForFgsStart)) {
return new BalVerdict(balImprovedMetrics() ? BAL_ALLOW_TOKEN : BAL_ALLOW_PERMISSION,
- /*background*/ true, "process allowed by token");
+ /*background*/ "process allowed by token");
}
// Allow if the caller is bound by a UID that's currently foreground.
// But still respect the appSwitchState.
if (checkConfiguration.checkVisibility && appSwitchState != APP_SWITCH_DISALLOW
&& isBoundByForegroundUid()) {
return new BalVerdict(balImprovedMetrics() ? BAL_ALLOW_BOUND_BY_FOREGROUND
- : BAL_ALLOW_VISIBLE_WINDOW, /*background*/ false,
+ : BAL_ALLOW_VISIBLE_WINDOW, /*background*/
"process bound by foreground uid");
}
// Allow if the caller has an activity in any foreground task.
if (checkConfiguration.checkOtherExemptions && hasActivityInVisibleTask
&& appSwitchState != APP_SWITCH_DISALLOW) {
- return new BalVerdict(BAL_ALLOW_FOREGROUND, /*background*/ false,
+ return new BalVerdict(BAL_ALLOW_FOREGROUND, /*background*/
"process has activity in foreground task");
}
@@ -160,7 +160,7 @@ class BackgroundLaunchProcessController {
long timeSinceLastStartOrFinish = now - Math.max(lastActivityLaunchTime,
lastActivityFinishTime);
if (timeSinceLastStartOrFinish < checkConfiguration.gracePeriod) {
- return new BalVerdict(BAL_ALLOW_GRACE_PERIOD, /*background*/ true,
+ return new BalVerdict(BAL_ALLOW_GRACE_PERIOD, /*background*/
"within " + checkConfiguration.gracePeriod + "ms grace period ("
+ timeSinceLastStartOrFinish + "ms)");
}
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/ClientLifecycleManager.java b/services/core/java/com/android/server/wm/ClientLifecycleManager.java
index d291d99f2a7a..98e453b3ff89 100644
--- a/services/core/java/com/android/server/wm/ClientLifecycleManager.java
+++ b/services/core/java/com/android/server/wm/ClientLifecycleManager.java
@@ -25,6 +25,7 @@ import android.app.servertransaction.LaunchActivityItem;
import android.compat.annotation.ChangeId;
import android.compat.annotation.EnabledSince;
import android.os.Build;
+import android.os.DeadObjectException;
import android.os.IBinder;
import android.os.RemoteException;
import android.os.Trace;
@@ -66,64 +67,80 @@ class ClientLifecycleManager {
/**
* Schedules a transaction, which may consist of multiple callbacks and a lifecycle request.
* @param transaction A sequence of client transaction items.
- * @throws RemoteException
- *
+ * @return {@code false} if the transaction failed because of {@link RemoteException}.
* @see ClientTransaction
*/
@VisibleForTesting
- void scheduleTransaction(@NonNull ClientTransaction transaction) throws RemoteException {
- final IApplicationThread client = transaction.getClient();
- try {
- transaction.schedule();
- } catch (RemoteException e) {
- Slog.w(TAG, "Failed to deliver transaction for " + client
- + "\ntransaction=" + transaction);
- throw e;
+ boolean scheduleTransaction(@NonNull ClientTransaction transaction) {
+ final RemoteException e = transaction.schedule();
+ if (e != null) {
+ final WindowProcessController wpc = mWms.mAtmService.getProcessController(
+ transaction.getClient());
+ Slog.w(TAG, "Failed to deliver transaction for " + wpc + "\ntransaction=" + this, e);
+ return false;
}
+ return true;
}
/**
* Similar to {@link #scheduleTransactionItem}, but it sends the transaction immediately and
* it can be called without WM lock.
*
+ * @return {@code false} if the transaction failed because of {@link RemoteException}.
* @see WindowProcessController#setReportedProcState(int)
*/
- void scheduleTransactionItemNow(@NonNull IApplicationThread client,
+ boolean scheduleTransactionItemNow(@NonNull IApplicationThread client,
@NonNull ClientTransactionItem transactionItem) throws RemoteException {
final ClientTransaction clientTransaction = new ClientTransaction(client);
clientTransaction.addTransactionItem(transactionItem);
- scheduleTransaction(clientTransaction);
+ final boolean res = scheduleTransaction(clientTransaction);
+ if (!com.android.window.flags.Flags.cleanupDispatchPendingTransactionsRemoteException()
+ && !res) {
+ throw new DeadObjectException();
+ }
+ return res;
}
/**
- * Schedules a transaction with the given item, delivery to client application.
+ * Schedules a transaction with the given item, delivery to client application, which may be
+ * queued to dispatched later.
*
- * @throws RemoteException
+ * @return {@code false} if the transaction was dispatched immediately, but failed because of
+ * {@link RemoteException}. If the transaction is queued, any failure will be ignored.
* @see ClientTransactionItem
*/
- void scheduleTransactionItem(@NonNull IApplicationThread client,
+ boolean scheduleTransactionItem(@NonNull IApplicationThread client,
@NonNull ClientTransactionItem item) throws RemoteException {
// Wait until RootWindowContainer#performSurfacePlacementNoTrace to dispatch all pending
// transactions at once.
final ClientTransaction clientTransaction = getOrCreatePendingTransaction(client);
clientTransaction.addTransactionItem(item);
- onClientTransactionItemScheduled(clientTransaction, false /* shouldDispatchImmediately */);
+ final boolean res = onClientTransactionItemScheduled(clientTransaction,
+ false /* shouldDispatchImmediately */);
+ if (!com.android.window.flags.Flags.cleanupDispatchPendingTransactionsRemoteException()
+ && !res) {
+ throw new DeadObjectException();
+ }
+ return res;
}
/**
- * Schedules a transaction with the given items, delivery to client application.
+ * Schedules a transaction with the given items, delivery to client application, which may be
+ * queued to dispatched later.
*
- * @throws RemoteException
+ * @return {@code false} if the transaction was dispatched immediately, but failed because of
+ * {@link RemoteException}. If the transaction is queued, any failure will be ignored.
* @see ClientTransactionItem
*/
- void scheduleTransactionItems(@NonNull IApplicationThread client,
+ boolean scheduleTransactionItems(@NonNull IApplicationThread client,
@NonNull ClientTransactionItem... items) throws RemoteException {
- scheduleTransactionItems(client, false /* shouldDispatchImmediately */, items);
+ return scheduleTransactionItems(client, false /* shouldDispatchImmediately */, items);
}
/**
- * Schedules a transaction with the given items, delivery to client application.
+ * Schedules a transaction with the given items, delivery to client application, which may be
+ * queued to dispatched later.
*
* @param shouldDispatchImmediately whether or not to dispatch the transaction immediately. This
* should only be {@code true} when it is important to know the
@@ -131,11 +148,11 @@ class ClientLifecycleManager {
* launches an app, the server needs to know if the transaction
* is dispatched successfully, and may restart the process if
* not.
- *
- * @throws RemoteException
+ * @return {@code false} if the transaction was dispatched immediately, but failed because of
+ * {@link RemoteException}. If the transaction is queued, any failure will be ignored.
* @see ClientTransactionItem
*/
- void scheduleTransactionItems(@NonNull IApplicationThread client,
+ boolean scheduleTransactionItems(@NonNull IApplicationThread client,
boolean shouldDispatchImmediately,
@NonNull ClientTransactionItem... items) throws RemoteException {
// Wait until RootWindowContainer#performSurfacePlacementNoTrace to dispatch all pending
@@ -147,7 +164,13 @@ class ClientLifecycleManager {
clientTransaction.addTransactionItem(items[i]);
}
- onClientTransactionItemScheduled(clientTransaction, shouldDispatchImmediately);
+ final boolean res = onClientTransactionItemScheduled(clientTransaction,
+ shouldDispatchImmediately);
+ if (!com.android.window.flags.Flags.cleanupDispatchPendingTransactionsRemoteException()
+ && !res) {
+ throw new DeadObjectException();
+ }
+ return res;
}
/** Executes all the pending transactions. */
@@ -159,12 +182,7 @@ class ClientLifecycleManager {
final int size = mPendingTransactions.size();
for (int i = 0; i < size; i++) {
final ClientTransaction transaction = mPendingTransactions.valueAt(i);
- try {
- scheduleTransaction(transaction);
- } catch (RemoteException e) {
- Slog.e(TAG, "Failed to deliver pending transaction", e);
- // TODO(b/323801078): apply cleanup for individual transaction item if needed.
- }
+ scheduleTransaction(transaction);
}
mPendingTransactions.clear();
Trace.traceEnd(Trace.TRACE_TAG_WINDOW_MANAGER);
@@ -174,12 +192,7 @@ class ClientLifecycleManager {
void dispatchPendingTransaction(@NonNull IApplicationThread client) {
final ClientTransaction pendingTransaction = mPendingTransactions.remove(client.asBinder());
if (pendingTransaction != null) {
- try {
- scheduleTransaction(pendingTransaction);
- } catch (RemoteException e) {
- Slog.e(TAG, "Failed to deliver pending transaction", e);
- // TODO(b/323801078): apply cleanup for individual transaction item if needed.
- }
+ scheduleTransaction(pendingTransaction);
}
}
@@ -211,15 +224,22 @@ class ClientLifecycleManager {
return transaction;
}
- /** Must only be called with WM lock. */
- private void onClientTransactionItemScheduled(
+ /**
+ * Must only be called with WM lock.
+ * If the transaction should not be queued, it will be dispatched immediately.
+ *
+ * @return {@code false} if the transaction was dispatched immediately, but failed because of
+ * {@link RemoteException}.
+ */
+ private boolean onClientTransactionItemScheduled(
@NonNull ClientTransaction clientTransaction,
- boolean shouldDispatchImmediately) throws RemoteException {
+ boolean shouldDispatchImmediately) {
if (shouldDispatchImmediately || shouldDispatchPendingTransactionsImmediately()) {
// Dispatch the pending transaction immediately.
mPendingTransactions.remove(clientTransaction.getClient().asBinder());
- scheduleTransaction(clientTransaction);
+ return scheduleTransaction(clientTransaction);
}
+ return true;
}
/** Must only be called with WM lock. */
diff --git a/services/core/java/com/android/server/wm/ContentRecorder.java b/services/core/java/com/android/server/wm/ContentRecorder.java
index d6ae65193121..c26acec19743 100644
--- a/services/core/java/com/android/server/wm/ContentRecorder.java
+++ b/services/core/java/com/android/server/wm/ContentRecorder.java
@@ -490,7 +490,7 @@ final class ContentRecorder implements WindowContainerListener {
return null;
}
final Task taskToRecord = wc.asTask();
- if (taskToRecord == null) {
+ if (taskToRecord == null || !taskToRecord.isAttached()) {
handleStartRecordingFailed();
ProtoLog.v(WM_DEBUG_CONTENT_RECORDING,
"Content Recording: Unable to retrieve task to start recording for "
diff --git a/services/core/java/com/android/server/wm/DeferredDisplayUpdater.java b/services/core/java/com/android/server/wm/DeferredDisplayUpdater.java
index f473b7b7e4fb..fcc697242ff6 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;
@@ -140,8 +140,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 +152,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 +206,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 +216,6 @@ class DeferredDisplayUpdater {
try {
onStartCollect.run();
- ProtoLog.d(WM_DEBUG_WINDOW_TRANSITIONS,
- "DeferredDisplayUpdater: applied DisplayInfo after deferring");
-
if (physicalDisplayUpdated) {
onDisplayUpdated(transition, fromRotation, startBounds);
} else {
diff --git a/services/core/java/com/android/server/wm/DesktopModeBoundsCalculator.java b/services/core/java/com/android/server/wm/DesktopModeBoundsCalculator.java
index 0106a64660fe..7a959c14fbd2 100644
--- a/services/core/java/com/android/server/wm/DesktopModeBoundsCalculator.java
+++ b/services/core/java/com/android/server/wm/DesktopModeBoundsCalculator.java
@@ -52,6 +52,13 @@ public final class DesktopModeBoundsCalculator {
.getInt("persist.wm.debug.desktop_mode_landscape_app_padding", 25);
/**
+ * Proportion of window height top offset with respect to bottom offset, used for central task
+ * positioning. Should be kept in sync with constant in
+ * {@link com.android.wm.shell.desktopmode.DesktopTaskPosition}
+ */
+ public static final float WINDOW_HEIGHT_PROPORTION = 0.375f;
+
+ /**
* Updates launch bounds for an activity with respect to its activity options, window layout,
* android manifest and task configuration.
*/
@@ -63,7 +70,16 @@ public final class DesktopModeBoundsCalculator {
final Rect stableBounds = new Rect();
task.getDisplayArea().getStableRect(stableBounds);
- if (options != null && options.getLaunchBounds() != null) {
+ // If the options bounds size is flexible, update size with calculated desired size.
+ final boolean updateOptionBoundsSize = options != null
+ && options.getFlexibleLaunchSize();
+ // 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();
+
+ if (options != null && options.getLaunchBounds() != null
+ && !updateOptionBoundsSize) {
outBounds.set(options.getLaunchBounds());
logger.accept("inherit-from-options=" + outBounds);
} else if (layout != null) {
@@ -76,26 +92,34 @@ public final class DesktopModeBoundsCalculator {
stableBounds);
logger.accept("layout specifies sizes, inheriting size and applying gravity");
} else if (verticalGravity > 0 || horizontalGravity > 0) {
- outBounds.set(calculateInitialBounds(task, activity, stableBounds));
+ outBounds.set(calculateInitialBounds(task, activity, stableBounds, options,
+ shouldRespectOptionPosition));
applyLayoutGravity(verticalGravity, horizontalGravity, outBounds,
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));
+ outBounds.set(calculateInitialBounds(task, activity, stableBounds, options,
+ shouldRespectOptionPosition));
logger.accept("layout not specified, applying desired bounds");
+ logger.accept("respecting option bounds cascaded position="
+ + shouldRespectOptionPosition);
}
}
/**
* Calculates the initial bounds required for an application to fill a scale of the display
* bounds without any letterboxing. This is done by taking into account the applications
- * fullscreen size, aspect ratio, orientation and resizability to calculate an area this is
- * compatible with the applications previous configuration.
+ * fullscreen size, aspect ratio, orientation and resizability to calculate an area that is
+ * compatible with the applications previous configuration. These bounds are then cascaded to
+ * either the center or to a cascading position supplied by Shell via the option bounds.
*/
@NonNull
private static Rect calculateInitialBounds(@NonNull Task task,
- @NonNull ActivityRecord activity, @NonNull Rect stableBounds
+ @NonNull ActivityRecord activity, @NonNull Rect stableBounds,
+ @Nullable ActivityOptions options, boolean shouldRespectOptionPosition
) {
// Display bounds not taking into account insets.
final TaskDisplayArea displayArea = task.getDisplayArea();
@@ -172,6 +196,9 @@ public final class DesktopModeBoundsCalculator {
}
default -> idealSize;
};
+ if (shouldRespectOptionPosition) {
+ return respectShellCascading(initialSize, stableBounds, options.getLaunchBounds());
+ }
return centerInScreen(initialSize, screenBounds);
}
@@ -266,14 +293,65 @@ public final class DesktopModeBoundsCalculator {
* Adjusts bounds to be positioned in the middle of the screen.
*/
@NonNull
- private static Rect centerInScreen(@NonNull Size desiredSize,
+ static Rect centerInScreen(@NonNull Size desiredSize,
@NonNull Rect screenBounds) {
- // TODO(b/325240051): Position apps with bottom heavy offset
- final int heightOffset = (screenBounds.height() - desiredSize.getHeight()) / 2;
+ final int heightOffset = (int)
+ ((screenBounds.height() - desiredSize.getHeight()) * WINDOW_HEIGHT_PROPORTION);
final int widthOffset = (screenBounds.width() - desiredSize.getWidth()) / 2;
final Rect resultBounds = new Rect(0, 0,
desiredSize.getWidth(), desiredSize.getHeight());
resultBounds.offset(screenBounds.left + widthOffset, screenBounds.top + heightOffset);
return resultBounds;
}
+
+ /**
+ * Calculates final initial bounds based on the task's desired size and the cascading anchor
+ * point extracted from the option bounds. Cascading behaviour should be kept in sync with
+ * logic in {@link com.android.wm.shell.desktopmode.DesktopTaskPosition}.
+ */
+ @NonNull
+ private static Rect respectShellCascading(@NonNull Size desiredSize, @NonNull Rect stableBounds,
+ @NonNull Rect optionBounds) {
+ final boolean leftAligned = stableBounds.left == optionBounds.left;
+ final boolean rightAligned = stableBounds.right == optionBounds.right;
+ final boolean topAligned = stableBounds.top == optionBounds.top;
+ final boolean bottomAligned = stableBounds.bottom == optionBounds.bottom;
+ if (leftAligned && topAligned) {
+ // Bounds cascaded to top left, respect top left position anchor point.
+ return new Rect(
+ optionBounds.left,
+ optionBounds.top,
+ optionBounds.left + desiredSize.getWidth(),
+ optionBounds.top + desiredSize.getHeight()
+ );
+ }
+ if (leftAligned && bottomAligned) {
+ // Bounds cascaded to bottom left, respect bottom left position anchor point.
+ return new Rect(
+ optionBounds.left,
+ optionBounds.bottom - desiredSize.getHeight(),
+ optionBounds.left + desiredSize.getWidth(),
+ optionBounds.bottom
+ );
+ }
+ if (rightAligned && topAligned) {
+ // Bounds cascaded to top right, respect top right position anchor point.
+ return new Rect(
+ optionBounds.right - desiredSize.getWidth(),
+ optionBounds.top,
+ optionBounds.right,
+ optionBounds.top + desiredSize.getHeight()
+ );
+ }
+ if (rightAligned && bottomAligned) {
+ // Bounds cascaded to bottom right, respect bottom right position anchor point.
+ return new Rect(
+ optionBounds.right - desiredSize.getWidth(),
+ optionBounds.bottom - desiredSize.getHeight(),
+ optionBounds.right,
+ optionBounds.bottom);
+ }
+ // Bounds not cascaded to any corner, default to center position.
+ return centerInScreen(desiredSize, stableBounds);
+ }
}
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 a4eeb688162c..ddcb5eccb1d8 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;
@@ -42,6 +48,7 @@ class DesktopModeLaunchParamsModifier implements LaunchParamsModifier {
private static final String TAG =
TAG_WITH_CLASS_NAME ? "DesktopModeLaunchParamsModifier" : TAG_ATM;
+
private static final boolean DEBUG = false;
private StringBuilder mLogBuilder;
@@ -130,9 +137,29 @@ class DesktopModeLaunchParamsModifier implements LaunchParamsModifier {
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);
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
+ // applying further cascading. Since other modifiers are being skipped in this case,
+ // this modifier is now also responsible to respecting the options launch windowing
+ // mode.
+ outParams.mWindowingMode = options.getLaunchWindowingMode();
+ return RESULT_DONE;
+ }
return RESULT_CONTINUE;
}
@@ -150,7 +177,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);
}
@@ -192,6 +219,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/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java
index 64c19ff70c9f..353ccd5836c8 100644
--- a/services/core/java/com/android/server/wm/DisplayContent.java
+++ b/services/core/java/com/android/server/wm/DisplayContent.java
@@ -82,7 +82,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 +99,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 +106,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 +116,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 +154,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 +234,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 +246,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 +364,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 +1180,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);
@@ -3154,7 +3154,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 +3189,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 +3258,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,9 +3276,21 @@ 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);
}
/**
+ * 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.
@@ -3295,6 +3306,10 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp
return false;
}
+ if (shouldNeverShowSystemDecorations()) {
+ return false;
+ }
+
// TODO(b/391965805): Remove this after introducing FLAG_ALLOW_CONTENT_MODE_SWITCH.
if ((mDisplay.getFlags() & Display.FLAG_REAR) != 0) {
return false;
@@ -3612,18 +3627,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);
@@ -5634,29 +5651,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 +5674,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)
@@ -6544,19 +6550,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();
}
/**
@@ -6575,6 +6571,22 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp
.getKeyguardController().isKeyguardLocked(mDisplayId);
}
+ boolean isKeyguardLockedOrAodShowing() {
+ return isKeyguardLocked() || isAodShowing();
+ }
+
+ /**
+ * @return whether aod is showing for this display
+ */
+ boolean isAodShowing() {
+ final boolean isAodShowing = mRootWindowContainer.mTaskSupervisor
+ .getKeyguardController().isAodShowing(mDisplayId);
+ if (mDisplayId == DEFAULT_DISPLAY && isAodShowing) {
+ return !isKeyguardGoingAway();
+ }
+ return isAodShowing;
+ }
+
/**
* @return whether keyguard is going away on this display
*/
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/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..2cac63c1e5e9 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);
}
}
@@ -774,7 +777,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/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..b7489029768a 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;
@@ -803,14 +803,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 +823,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/KeyguardController.java b/services/core/java/com/android/server/wm/KeyguardController.java
index 6d73739e5046..ff1e400a4a23 100644
--- a/services/core/java/com/android/server/wm/KeyguardController.java
+++ b/services/core/java/com/android/server/wm/KeyguardController.java
@@ -18,6 +18,7 @@ package com.android.server.wm;
import static android.os.Trace.TRACE_TAG_WINDOW_MANAGER;
import static android.view.Display.DEFAULT_DISPLAY;
+import static android.view.WindowManager.TRANSIT_FLAG_AOD_APPEARING;
import static android.view.WindowManager.TRANSIT_FLAG_KEYGUARD_APPEARING;
import static android.view.WindowManager.TRANSIT_FLAG_KEYGUARD_GOING_AWAY;
import static android.view.WindowManager.TRANSIT_FLAG_KEYGUARD_GOING_AWAY_NO_ANIMATION;
@@ -27,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;
@@ -216,6 +216,9 @@ class KeyguardController {
} else if (keyguardShowing && !state.mKeyguardShowing) {
transition.addFlag(TRANSIT_FLAG_KEYGUARD_APPEARING);
}
+ if (mWindowManager.mFlags.mAodTransition && aodShowing && !state.mAodShowing) {
+ transition.addFlag(TRANSIT_FLAG_AOD_APPEARING);
+ }
}
}
// Update the task snapshot if the screen will not be turned off. To make sure that the
@@ -238,19 +241,28 @@ class KeyguardController {
state.mAodShowing = aodShowing;
state.writeEventLog("setKeyguardShown");
- if (keyguardChanged) {
- // Irrelevant to AOD.
- state.mKeyguardGoingAway = false;
- if (keyguardShowing) {
- state.mDismissalRequested = false;
+ if (keyguardChanged || (mWindowManager.mFlags.mAodTransition && aodChanged)) {
+ if (keyguardChanged) {
+ // Irrelevant to AOD.
+ state.mKeyguardGoingAway = false;
+ if (keyguardShowing) {
+ state.mDismissalRequested = false;
+ }
}
if (goingAwayRemoved
- || (keyguardShowing && !Display.isOffState(dc.getDisplayInfo().state))) {
+ || (keyguardShowing && !Display.isOffState(dc.getDisplayInfo().state))
+ || (mWindowManager.mFlags.mAodTransition && aodShowing)) {
// Keyguard decided to show or stopped going away. Send a transition to animate back
// to the locked state before holding the sleep token again
if (!ENABLE_NEW_KEYGUARD_SHELL_TRANSITIONS) {
- dc.requestTransitionAndLegacyPrepare(
- TRANSIT_TO_FRONT, TRANSIT_FLAG_KEYGUARD_APPEARING, /* trigger= */ null);
+ if (keyguardChanged) {
+ dc.requestTransitionAndLegacyPrepare(TRANSIT_TO_FRONT,
+ TRANSIT_FLAG_KEYGUARD_APPEARING, /* trigger= */ null);
+ }
+ if (mWindowManager.mFlags.mAodTransition && aodChanged && aodShowing) {
+ dc.requestTransitionAndLegacyPrepare(TRANSIT_TO_FRONT,
+ TRANSIT_FLAG_AOD_APPEARING, /* trigger= */ null);
+ }
}
dc.mWallpaperController.adjustWallpaperWindows();
dc.executeAppTransition();
@@ -297,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.
@@ -477,8 +488,6 @@ class KeyguardController {
if (trigger != null) {
transition.collect(trigger);
}
- } else {
- dc.prepareAppTransition(transitType, transitFlags);
}
} else {
if (tc.inTransition()) {
@@ -502,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.
@@ -529,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/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/RootWindowContainer.java b/services/core/java/com/android/server/wm/RootWindowContainer.java
index e864ecf60770..609302ce3f56 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;
@@ -1371,7 +1371,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 +1732,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 +2760,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 +2819,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/SurfaceAnimationRunner.java b/services/core/java/com/android/server/wm/SurfaceAnimationRunner.java
index bbc35a35e93f..fba29d47d4d4 100644
--- a/services/core/java/com/android/server/wm/SurfaceAnimationRunner.java
+++ b/services/core/java/com/android/server/wm/SurfaceAnimationRunner.java
@@ -16,7 +16,6 @@
package com.android.server.wm;
-import static android.os.Trace.TRACE_TAG_WINDOW_MANAGER;
import static android.util.TimeUtils.NANOS_PER_MS;
import static android.view.Choreographer.CALLBACK_TRAVERSAL;
import static android.view.Choreographer.getSfInstance;
@@ -27,25 +26,13 @@ import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.animation.ValueAnimator;
import android.annotation.Nullable;
-import android.graphics.BitmapShader;
-import android.graphics.Canvas;
-import android.graphics.Insets;
-import android.graphics.Paint;
-import android.graphics.PixelFormat;
-import android.graphics.Rect;
import android.hardware.power.Boost;
import android.os.Handler;
import android.os.PowerManagerInternal;
-import android.os.Trace;
import android.util.ArrayMap;
-import android.util.Log;
import android.view.Choreographer;
-import android.view.Surface;
import android.view.SurfaceControl;
import android.view.SurfaceControl.Transaction;
-import android.view.animation.Animation;
-import android.view.animation.Transformation;
-import android.window.ScreenCapture;
import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
@@ -53,9 +40,6 @@ import com.android.internal.graphics.SfVsyncFrameCallbackProvider;
import com.android.server.AnimationThread;
import com.android.server.wm.LocalAnimationAdapter.AnimationSpec;
-import java.util.ArrayList;
-import java.util.concurrent.ExecutorService;
-import java.util.concurrent.Executors;
import java.util.function.Supplier;
/**
@@ -73,12 +57,6 @@ class SurfaceAnimationRunner {
*/
private final Object mCancelLock = new Object();
- /**
- * Lock for synchronizing {@link #mEdgeExtensions} to prevent race conditions when managing
- * created edge extension surfaces.
- */
- private final Object mEdgeExtensionLock = new Object();
-
@VisibleForTesting
Choreographer mChoreographer;
@@ -91,12 +69,6 @@ class SurfaceAnimationRunner {
private final PowerManagerInternal mPowerManagerInternal;
private boolean mApplyScheduled;
- // Executor to perform the edge extension.
- // With two threads because in practice we will want to extend two surfaces in one animation,
- // in which case we want to be able to parallelize those two extensions to cut down latency in
- // starting the animation.
- private final ExecutorService mEdgeExtensionExecutor = Executors.newFixedThreadPool(2);
-
@GuardedBy("mLock")
@VisibleForTesting
final ArrayMap<SurfaceControl, RunningAnimation> mPendingAnimations = new ArrayMap<>();
@@ -112,11 +84,6 @@ class SurfaceAnimationRunner {
@GuardedBy("mLock")
private boolean mAnimationStartDeferred;
- // Mapping animation leashes to a list of edge extension surfaces associated with them
- @GuardedBy("mEdgeExtensionLock")
- private final ArrayMap<SurfaceControl, ArrayList<SurfaceControl>> mEdgeExtensions =
- new ArrayMap<>();
-
/**
* There should only ever be one instance of this class. Usual spot for it is with
* {@link WindowManagerService}
@@ -175,64 +142,9 @@ class SurfaceAnimationRunner {
synchronized (mLock) {
final RunningAnimation runningAnim = new RunningAnimation(a, animationLeash,
finishCallback);
- boolean requiresEdgeExtension = requiresEdgeExtension(a);
-
- if (requiresEdgeExtension) {
- final ArrayList<SurfaceControl> extensionSurfaces = new ArrayList<>();
- synchronized (mEdgeExtensionLock) {
- mEdgeExtensions.put(animationLeash, extensionSurfaces);
- }
-
- mPreProcessingAnimations.put(animationLeash, runningAnim);
-
- // We must wait for t to be committed since otherwise the leash doesn't have the
- // windows we want to screenshot and extend as children.
- t.addTransactionCommittedListener(mEdgeExtensionExecutor, () -> {
- if (!animationLeash.isValid()) {
- Log.e(TAG, "Animation leash is not valid");
- synchronized (mEdgeExtensionLock) {
- mEdgeExtensions.remove(animationLeash);
- }
- synchronized (mLock) {
- mPreProcessingAnimations.remove(animationLeash);
- }
- return;
- }
- final WindowAnimationSpec animationSpec = a.asWindowAnimationSpec();
-
- final Transaction edgeExtensionCreationTransaction = new Transaction();
- edgeExtendWindow(animationLeash,
- animationSpec.getRootTaskBounds(), animationSpec.getAnimation(),
- edgeExtensionCreationTransaction);
-
- synchronized (mLock) {
- // only run if animation is not yet canceled by this point
- if (mPreProcessingAnimations.get(animationLeash) == runningAnim) {
- // In the case the animation is cancelled, edge extensions are removed
- // onAnimationLeashLost which is called before onAnimationCancelled.
- // So we need to check if the edge extensions have already been removed
- // or not, and if so we don't want to apply the transaction.
- synchronized (mEdgeExtensionLock) {
- if (!mEdgeExtensions.isEmpty()) {
- edgeExtensionCreationTransaction.apply();
- }
- }
-
- mPreProcessingAnimations.remove(animationLeash);
- mPendingAnimations.put(animationLeash, runningAnim);
- if (!mAnimationStartDeferred && mPreProcessingAnimations.isEmpty()) {
- mChoreographer.postFrameCallback(this::startAnimations);
- }
- }
- }
- });
- }
-
- if (!requiresEdgeExtension) {
- mPendingAnimations.put(animationLeash, runningAnim);
- if (!mAnimationStartDeferred && mPreProcessingAnimations.isEmpty()) {
- mChoreographer.postFrameCallback(this::startAnimations);
- }
+ mPendingAnimations.put(animationLeash, runningAnim);
+ if (!mAnimationStartDeferred && mPreProcessingAnimations.isEmpty()) {
+ mChoreographer.postFrameCallback(this::startAnimations);
}
// Some animations (e.g. move animations) require the initial transform to be
@@ -241,10 +153,6 @@ class SurfaceAnimationRunner {
}
}
- private boolean requiresEdgeExtension(AnimationSpec a) {
- return a.asWindowAnimationSpec() != null && a.asWindowAnimationSpec().hasExtension();
- }
-
void onAnimationCancelled(SurfaceControl leash) {
synchronized (mLock) {
if (mPendingAnimations.containsKey(leash)) {
@@ -374,161 +282,6 @@ class SurfaceAnimationRunner {
mApplyScheduled = false;
}
- private void edgeExtendWindow(SurfaceControl leash, Rect bounds, Animation a,
- Transaction transaction) {
- final Transformation transformationAtStart = new Transformation();
- a.getTransformationAt(0, transformationAtStart);
- final Transformation transformationAtEnd = new Transformation();
- a.getTransformationAt(1, transformationAtEnd);
-
- // We want to create an extension surface that is the maximal size and the animation will
- // take care of cropping any part that overflows.
- final Insets maxExtensionInsets = Insets.min(
- transformationAtStart.getInsets(), transformationAtEnd.getInsets());
-
- final int targetSurfaceHeight = bounds.height();
- final int targetSurfaceWidth = bounds.width();
-
- if (maxExtensionInsets.left < 0) {
- final Rect edgeBounds = new Rect(bounds.left, bounds.top, bounds.left + 1,
- bounds.bottom);
- final Rect extensionRect = new Rect(0, 0,
- -maxExtensionInsets.left, targetSurfaceHeight);
- final int xPos = bounds.left + maxExtensionInsets.left;
- final int yPos = bounds.top;
- createExtensionSurface(leash, edgeBounds,
- extensionRect, xPos, yPos, "Left Edge Extension", transaction);
- }
-
- if (maxExtensionInsets.top < 0) {
- final Rect edgeBounds = new Rect(bounds.left, bounds.top, targetSurfaceWidth,
- bounds.top + 1);
- final Rect extensionRect = new Rect(0, 0,
- targetSurfaceWidth, -maxExtensionInsets.top);
- final int xPos = bounds.left;
- final int yPos = bounds.top + maxExtensionInsets.top;
- createExtensionSurface(leash, edgeBounds,
- extensionRect, xPos, yPos, "Top Edge Extension", transaction);
- }
-
- if (maxExtensionInsets.right < 0) {
- final Rect edgeBounds = new Rect(bounds.right - 1, bounds.top, bounds.right,
- bounds.bottom);
- final Rect extensionRect = new Rect(0, 0,
- -maxExtensionInsets.right, targetSurfaceHeight);
- final int xPos = bounds.right;
- final int yPos = bounds.top;
- createExtensionSurface(leash, edgeBounds,
- extensionRect, xPos, yPos, "Right Edge Extension", transaction);
- }
-
- if (maxExtensionInsets.bottom < 0) {
- final Rect edgeBounds = new Rect(bounds.left, bounds.bottom - 1,
- bounds.right, bounds.bottom);
- final Rect extensionRect = new Rect(0, 0,
- targetSurfaceWidth, -maxExtensionInsets.bottom);
- final int xPos = bounds.left;
- final int yPos = bounds.bottom;
- createExtensionSurface(leash, edgeBounds,
- extensionRect, xPos, yPos, "Bottom Edge Extension", transaction);
- }
- }
-
- private void createExtensionSurface(SurfaceControl leash, Rect edgeBounds,
- Rect extensionRect, int xPos, int yPos, String layerName,
- Transaction startTransaction) {
- Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "createExtensionSurface");
- doCreateExtensionSurface(leash, edgeBounds, extensionRect, xPos, yPos, layerName,
- startTransaction);
- Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
- }
-
- private void doCreateExtensionSurface(SurfaceControl leash, Rect edgeBounds,
- Rect extensionRect, int xPos, int yPos, String layerName,
- Transaction startTransaction) {
- ScreenCapture.LayerCaptureArgs captureArgs =
- new ScreenCapture.LayerCaptureArgs.Builder(leash /* surfaceToExtend */)
- .setSourceCrop(edgeBounds)
- .setFrameScale(1)
- .setPixelFormat(PixelFormat.RGBA_8888)
- .setChildrenOnly(true)
- .setAllowProtected(true)
- .setCaptureSecureLayers(true)
- .build();
- final ScreenCapture.ScreenshotHardwareBuffer edgeBuffer =
- ScreenCapture.captureLayers(captureArgs);
-
- if (edgeBuffer == null) {
- // The leash we are trying to screenshot may have been removed by this point, which is
- // likely the reason for ending up with a null edgeBuffer, in which case we just want to
- // return and do nothing.
- Log.e(TAG, "Failed to create edge extension - edge buffer is null");
- return;
- }
-
- final SurfaceControl edgeExtensionLayer = new SurfaceControl.Builder()
- .setName(layerName)
- .setHidden(true)
- .setCallsite("DefaultTransitionHandler#startAnimation")
- .setOpaque(true)
- .setBufferSize(extensionRect.width(), extensionRect.height())
- .build();
-
- BitmapShader shader = new BitmapShader(edgeBuffer.asBitmap(),
- android.graphics.Shader.TileMode.CLAMP,
- android.graphics.Shader.TileMode.CLAMP);
- final Paint paint = new Paint();
- paint.setShader(shader);
-
- final Surface surface = new Surface(edgeExtensionLayer);
- Canvas c = surface.lockHardwareCanvas();
- c.drawRect(extensionRect, paint);
- surface.unlockCanvasAndPost(c);
- surface.release();
-
- synchronized (mEdgeExtensionLock) {
- if (!mEdgeExtensions.containsKey(leash)) {
- // The animation leash has already been removed, so we don't want to attach the
- // edgeExtension layer and should immediately remove it instead.
- startTransaction.remove(edgeExtensionLayer);
- return;
- }
-
- startTransaction.reparent(edgeExtensionLayer, leash);
- startTransaction.setLayer(edgeExtensionLayer, Integer.MIN_VALUE);
- startTransaction.setPosition(edgeExtensionLayer, xPos, yPos);
- startTransaction.setVisibility(edgeExtensionLayer, true);
-
- mEdgeExtensions.get(leash).add(edgeExtensionLayer);
- }
- }
-
- private float getScaleXForExtensionSurface(Rect edgeBounds, Rect extensionRect) {
- if (edgeBounds.width() == extensionRect.width()) {
- // Top or bottom edge extension, no need to scale the X axis of the extension surface.
- return 1;
- }
- if (edgeBounds.width() == 1) {
- // Left or right edge extension, scale the surface to be the extensionRect's width.
- return extensionRect.width();
- }
-
- throw new RuntimeException("Unexpected edgeBounds and extensionRect widths");
- }
-
- private float getScaleYForExtensionSurface(Rect edgeBounds, Rect extensionRect) {
- if (edgeBounds.height() == extensionRect.height()) {
- // Left or right edge extension, no need to scale the Y axis of the extension surface.
- return 1;
- }
- if (edgeBounds.height() == 1) {
- // Top or bottom edge extension, scale the surface to be the extensionRect's height.
- return extensionRect.height();
- }
-
- throw new RuntimeException("Unexpected edgeBounds and extensionRect heights");
- }
-
private static final class RunningAnimation {
final AnimationSpec mAnimSpec;
final SurfaceControl mLeash;
@@ -545,22 +298,6 @@ class SurfaceAnimationRunner {
}
}
- protected void onAnimationLeashLost(SurfaceControl animationLeash,
- Transaction t) {
- synchronized (mEdgeExtensionLock) {
- if (!mEdgeExtensions.containsKey(animationLeash)) {
- return;
- }
-
- final ArrayList<SurfaceControl> edgeExtensions = mEdgeExtensions.get(animationLeash);
- for (int i = 0; i < edgeExtensions.size(); i++) {
- final SurfaceControl extension = edgeExtensions.get(i);
- t.remove(extension);
- }
- mEdgeExtensions.remove(animationLeash);
- }
- }
-
@VisibleForTesting
interface AnimatorFactory {
ValueAnimator makeAnimator();
diff --git a/services/core/java/com/android/server/wm/Task.java b/services/core/java/com/android/server/wm/Task.java
index 22f0278b3a30..e98b2b749af8 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.
@@ -4497,7 +4466,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..2dabb253744a 100644
--- a/services/core/java/com/android/server/wm/TaskFragment.java
+++ b/services/core/java/com/android/server/wm/TaskFragment.java
@@ -88,6 +88,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 +235,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 +450,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 +462,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 +502,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 +1312,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 +1353,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 +1663,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 +1755,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 +1982,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);
@@ -3256,40 +3210,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 +3350,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 +3478,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 3db1d50f3d6a..803c21ccab6e 100644
--- a/services/core/java/com/android/server/wm/Transition.java
+++ b/services/core/java/com/android/server/wm/Transition.java
@@ -36,6 +36,7 @@ import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_STARTING;
import static android.view.WindowManager.LayoutParams.TYPE_INPUT_METHOD;
import static android.view.WindowManager.TRANSIT_CHANGE;
import static android.view.WindowManager.TRANSIT_CLOSE;
+import static android.view.WindowManager.TRANSIT_FLAG_AOD_APPEARING;
import static android.view.WindowManager.TRANSIT_FLAG_IS_RECENTS;
import static android.view.WindowManager.TRANSIT_FLAG_KEYGUARD_LOCKED;
import static android.view.WindowManager.TRANSIT_OPEN;
@@ -980,6 +981,10 @@ class Transition implements BLASTSyncEngine.TransactionReadyListener {
return false;
}
+ boolean isInAodAppearTransition() {
+ return (mFlags & TRANSIT_FLAG_AOD_APPEARING) != 0;
+ }
+
/**
* Specifies configuration change explicitly for the window container, so it can be chosen as
* transition target. This is usually used with transition mode
@@ -2584,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/TransitionController.java b/services/core/java/com/android/server/wm/TransitionController.java
index 11c5c9345ab2..9b3b4451a746 100644
--- a/services/core/java/com/android/server/wm/TransitionController.java
+++ b/services/core/java/com/android/server/wm/TransitionController.java
@@ -526,6 +526,19 @@ class TransitionController {
return false;
}
+ boolean isInAodAppearTransition() {
+ if (mCollectingTransition != null && mCollectingTransition.isInAodAppearTransition()) {
+ return true;
+ }
+ for (int i = mWaitingTransitions.size() - 1; i >= 0; --i) {
+ if (mWaitingTransitions.get(i).isInAodAppearTransition()) return true;
+ }
+ for (int i = mPlayingTransitions.size() - 1; i >= 0; --i) {
+ if (mPlayingTransitions.get(i).isInAodAppearTransition()) return true;
+ }
+ return false;
+ }
+
/**
* @return A pair of the transition and restore-behind target for the given {@param container}.
* @param container An ancestor of a transient-launch activity
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 a8b9fedcdc73..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());
@@ -166,17 +147,21 @@ class WallpaperController {
mFindResults.setWallpaperTarget(w);
return false;
}
+ } else if (mService.mFlags.mAodTransition
+ && mDisplayContent.isKeyguardLockedOrAodShowing()) {
+ if (mService.mPolicy.isKeyguardHostWindow(w.mAttrs)
+ && w.mTransitionController.isInAodAppearTransition()) {
+ if (DEBUG_WALLPAPER) Slog.v(TAG, "Found aod transition wallpaper target: " + w);
+ mFindResults.setWallpaperTarget(w);
+ return true;
+ }
}
- 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);
@@ -217,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) {
@@ -265,10 +252,6 @@ class WallpaperController {
return largestDisplaySize;
}
- void setWallpaperCropUtils(WallpaperCropUtils wallpaperCropUtils) {
- mWallpaperCropUtils = wallpaperCropUtils;
- }
-
WindowState getWallpaperTarget() {
return mWallpaperTarget;
}
@@ -371,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;
@@ -678,7 +657,8 @@ class WallpaperController {
private WallpaperWindowToken getTokenForTarget(WindowState target) {
if (target == null) return null;
WindowState window = mFindResults.getTopWallpaper(
- target.canShowWhenLocked() && mService.isKeyguardLocked());
+ (target.canShowWhenLocked() && mService.isKeyguardLocked())
+ || (mService.mFlags.mAodTransition && mDisplayContent.isAodShowing()));
return window == null ? null : window.mToken.asWallpaperToken();
}
@@ -721,7 +701,9 @@ class WallpaperController {
if (mFindResults.wallpaperTarget == null && mFindResults.useTopWallpaperAsTarget) {
mFindResults.setWallpaperTarget(
- mFindResults.getTopWallpaper(mDisplayContent.isKeyguardLocked()));
+ mFindResults.getTopWallpaper(mService.mFlags.mAodTransition
+ ? mDisplayContent.isKeyguardLockedOrAodShowing()
+ : mDisplayContent.isKeyguardLocked()));
}
}
@@ -885,11 +867,17 @@ class WallpaperController {
if (mDisplayContent.mWmService.mFlags.mEnsureWallpaperInTransitions) {
visibleRequested = mWallpaperTarget != null && mWallpaperTarget.isVisibleRequested();
}
- updateWallpaperTokens(visibleRequested, mDisplayContent.isKeyguardLocked());
+ updateWallpaperTokens(visibleRequested,
+ mService.mFlags.mAodTransition
+ ? mDisplayContent.isKeyguardLockedOrAodShowing()
+ : mDisplayContent.isKeyguardLocked());
ProtoLog.v(WM_DEBUG_WALLPAPER,
"Wallpaper at display %d - visibility: %b, keyguardLocked: %b",
- mDisplayContent.getDisplayId(), visible, mDisplayContent.isKeyguardLocked());
+ mDisplayContent.getDisplayId(), visible,
+ mService.mFlags.mAodTransition
+ ? mDisplayContent.isKeyguardLockedOrAodShowing()
+ : mDisplayContent.isKeyguardLocked());
if (visible && mLastFrozen != mFindResults.isWallpaperTargetForLetterbox) {
mLastFrozen = mFindResults.isWallpaperTargetForLetterbox;
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 1c3510d1b43f..772a7fdfc684 100644
--- a/services/core/java/com/android/server/wm/WindowContainer.java
+++ b/services/core/java/com/android/server/wm/WindowContainer.java
@@ -97,7 +97,6 @@ import com.android.server.wm.SurfaceAnimator.Animatable;
import com.android.server.wm.SurfaceAnimator.AnimationType;
import com.android.server.wm.SurfaceAnimator.OnAnimationFinishedCallback;
import com.android.server.wm.utils.AlwaysTruePredicate;
-import com.android.window.flags.Flags;
import java.io.PrintWriter;
import java.lang.ref.WeakReference;
@@ -2007,11 +2006,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 +2634,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 +2651,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
@@ -3067,7 +3071,6 @@ class WindowContainer<E extends WindowContainer> extends ConfigurationContainer<
@Override
public void onAnimationLeashLost(Transaction t) {
mLastLayer = -1;
- mWmService.mSurfaceAnimationRunner.onAnimationLeashLost(mAnimationLeash, t);
mAnimationLeash = null;
mNeedsZBoost = false;
reassignLayer(t);
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 28f2825150c2..c078d67b6cc6 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;
@@ -357,7 +356,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;
@@ -1080,14 +1078,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;
@@ -3237,49 +3233,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 +3360,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 +5304,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 +5360,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 +5647,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;
@@ -6863,7 +6835,7 @@ public class WindowManagerService extends IWindowManager.Stub
pw.print(' '); pw.println(imeControlTarget);
}
pw.print(" Minimum task size of display#"); pw.print(displayId);
- pw.print(' '); pw.print(dc.mMinSizeOfResizeableTaskDp);
+ pw.print(' '); pw.println(dc.mMinSizeOfResizeableTaskDp);
});
pw.print(" mBlurEnabled="); pw.println(mBlurController.getBlurEnabled());
pw.print(" mDisableSecureWindows="); pw.println(mDisableSecureWindows);
@@ -7968,7 +7940,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 +8099,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 +8655,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);
@@ -9392,23 +9367,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 +9437,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..a012ec137892 100644
--- a/services/core/java/com/android/server/wm/WindowOrganizerController.java
+++ b/services/core/java/com/android/server/wm/WindowOrganizerController.java
@@ -472,6 +472,7 @@ class WindowOrganizerController extends IWindowOrganizerController.Stub
transition.setAllReady();
}
+ // TODO(b/365884835): remove this method and callers.
@Override
public int startLegacyTransition(int type, @NonNull RemoteAnimationAdapter adapter,
@NonNull IWindowContainerTransactionCallback callback,
@@ -489,16 +490,6 @@ class WindowOrganizerController extends IWindowOrganizerController.Stub
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);
@@ -1680,13 +1671,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 +2216,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..1022d18ac0e9 100644
--- a/services/core/java/com/android/server/wm/WindowState.java
+++ b/services/core/java/com/android/server/wm/WindowState.java
@@ -5447,7 +5447,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/jni/Android.bp b/services/core/jni/Android.bp
index 95776088aad8..66d04df8095b 100644
--- a/services/core/jni/Android.bp
+++ b/services/core/jni/Android.bp
@@ -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",
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_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/stats/OWNERS b/services/core/jni/stats/OWNERS
index ab2d91adaeab..03086b3e44fc 100644
--- a/services/core/jni/stats/OWNERS
+++ b/services/core/jni/stats/OWNERS
@@ -1,7 +1,5 @@
jeffreyhuang@google.com
-jtnguyen@google.com
muhammadq@google.com
-sharaieko@google.com
singhtejinder@google.com
tsaichristine@google.com
yaochen@google.com
diff --git a/services/credentials/java/com/android/server/credentials/CredentialManagerService.java b/services/credentials/java/com/android/server/credentials/CredentialManagerService.java
index 014f0a2229c1..42e457c97fd4 100644
--- a/services/credentials/java/com/android/server/credentials/CredentialManagerService.java
+++ b/services/credentials/java/com/android/server/credentials/CredentialManagerService.java
@@ -31,6 +31,7 @@ import android.content.ContentResolver;
import android.content.Context;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
+import android.content.res.Resources;
import android.credentials.ClearCredentialStateRequest;
import android.credentials.CreateCredentialException;
import android.credentials.CreateCredentialRequest;
@@ -50,6 +51,7 @@ import android.credentials.ISetEnabledProvidersCallback;
import android.credentials.PrepareGetCredentialResponseInternal;
import android.credentials.RegisterCredentialDescriptionRequest;
import android.credentials.UnregisterCredentialDescriptionRequest;
+import android.credentials.flags.Flags;
import android.os.Binder;
import android.os.CancellationSignal;
import android.os.IBinder;
@@ -79,6 +81,7 @@ import java.util.HashSet;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
+import java.util.Objects;
import java.util.Set;
import java.util.function.Consumer;
import java.util.stream.Collectors;
@@ -535,6 +538,33 @@ public final class CredentialManagerService
final int userId = UserHandle.getCallingUserId();
final int callingUid = Binder.getCallingUid();
+ if (Flags.safeguardCandidateCredentialsApiCaller()) {
+ try {
+ String credentialManagerAutofillCompName = mContext.getResources().getString(
+ R.string.config_defaultCredentialManagerAutofillService);
+ ComponentName componentName = ComponentName.unflattenFromString(
+ credentialManagerAutofillCompName);
+ if (componentName == null) {
+ throw new SecurityException(
+ "Credential Autofill service does not exist on this device.");
+ }
+ PackageManager pm = mContext.createContextAsUser(
+ UserHandle.getUserHandleForUid(callingUid), 0).getPackageManager();
+ String callingProcessPackage = pm.getNameForUid(callingUid);
+ if (callingProcessPackage == null) {
+ throw new SecurityException(
+ "Couldn't determine the identity of the caller.");
+ }
+ if (!Objects.equals(componentName.getPackageName(), callingProcessPackage)) {
+ throw new SecurityException(callingProcessPackage
+ + " is not the device's credential autofill package.");
+ }
+ } catch (Resources.NotFoundException e) {
+ throw new SecurityException(
+ "Credential Autofill service does not exist on this device.");
+ }
+ }
+
// New request session, scoped for this request only.
final GetCandidateRequestSession session =
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 aee32a0473a3..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);
@@ -3582,14 +3606,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
@GuardedBy("getLockObject()")
private boolean maybeMigrateMemoryTaggingLocked(String backupId) {
- if (!Flags.setMtePolicyCoexistence()) {
- Slog.i(LOG_TAG, "Memory Tagging not migrated because coexistence "
- + "support is disabled.");
- return false;
- }
if (mOwners.isMemoryTaggingMigrated()) {
- // TODO: Remove log after Flags.setMtePolicyCoexistence full rollout.
- Slog.v(LOG_TAG, "Memory Tagging was previously migrated to policy engine.");
return false;
}
@@ -3789,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);
}
@@ -3977,6 +3995,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
deletedUsers.remove(userInfo.id);
}
for (Integer userId : deletedUsers) {
+ removeManagedEmbeddedSubscriptionsForUser(userId);
removeUserData(userId);
mDevicePolicyEngine.handleUserRemoved(userId);
}
@@ -8106,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) {
@@ -16354,7 +16412,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
private static <V> PolicyDefinition<V> getPolicyDefinitionForIdentifier(
@NonNull String identifier) {
Objects.requireNonNull(identifier);
- if (Flags.setMtePolicyCoexistence() && MEMORY_TAGGING_POLICY.equals(identifier)) {
+ if (MEMORY_TAGGING_POLICY.equals(identifier)) {
return (PolicyDefinition<V>) PolicyDefinition.MEMORY_TAGGING;
} else {
return (PolicyDefinition<V>) getPolicyDefinitionForRestriction(identifier);
@@ -19659,7 +19717,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
}
mInjector.getActivityManagerInternal()
- .setSwitchingFromSystemUserMessage(startUserSessionMessageString);
+ .setSwitchingFromUserMessage(caller.getUserId(), startUserSessionMessageString);
}
@Override
@@ -19684,7 +19742,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
}
mInjector.getActivityManagerInternal()
- .setSwitchingToSystemUserMessage(endUserSessionMessageString);
+ .setSwitchingToUserMessage(caller.getUserId(), endUserSessionMessageString);
}
@Override
@@ -23759,46 +23817,21 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
Preconditions.checkCallAuthorization(isDefaultDeviceOwner(caller));
}
- if (Flags.setMtePolicyCoexistence()) {
- enforcePermission(MANAGE_DEVICE_POLICY_MTE, caller.getPackageName(),
- UserHandle.USER_ALL);
- } else {
- Preconditions.checkCallAuthorization(
- isDefaultDeviceOwner(caller)
- || isProfileOwnerOfOrganizationOwnedDevice(caller));
- }
+ enforcePermission(MANAGE_DEVICE_POLICY_MTE, caller.getPackageName(),
+ UserHandle.USER_ALL);
synchronized (getLockObject()) {
- if (Flags.setMtePolicyCoexistence()) {
- final EnforcingAdmin admin = enforcePermissionAndGetEnforcingAdmin(null,
- MANAGE_DEVICE_POLICY_MTE, callerPackageName, caller.getUserId());
- if (flags != DevicePolicyManager.MTE_NOT_CONTROLLED_BY_POLICY) {
- mDevicePolicyEngine.setGlobalPolicy(
- PolicyDefinition.MEMORY_TAGGING,
- admin,
- new IntegerPolicyValue(flags));
- } else {
- mDevicePolicyEngine.removeGlobalPolicy(
- PolicyDefinition.MEMORY_TAGGING,
- admin);
- }
+ final EnforcingAdmin admin = enforcePermissionAndGetEnforcingAdmin(null,
+ MANAGE_DEVICE_POLICY_MTE, callerPackageName, caller.getUserId());
+ if (flags != DevicePolicyManager.MTE_NOT_CONTROLLED_BY_POLICY) {
+ mDevicePolicyEngine.setGlobalPolicy(
+ PolicyDefinition.MEMORY_TAGGING,
+ admin,
+ new IntegerPolicyValue(flags));
} else {
- ActiveAdmin admin =
- getDeviceOwnerOrProfileOwnerOfOrganizationOwnedDeviceLocked();
- if (admin != null) {
- final String memtagProperty = "arm64.memtag.bootctl";
- if (flags == DevicePolicyManager.MTE_ENABLED) {
- mInjector.systemPropertiesSet(memtagProperty, "memtag");
- } else if (flags == DevicePolicyManager.MTE_DISABLED) {
- mInjector.systemPropertiesSet(memtagProperty, "memtag-off");
- } else if (flags == DevicePolicyManager.MTE_NOT_CONTROLLED_BY_POLICY) {
- if (admin.mtePolicy != DevicePolicyManager.MTE_NOT_CONTROLLED_BY_POLICY) {
- mInjector.systemPropertiesSet(memtagProperty, "default");
- }
- }
- admin.mtePolicy = flags;
- saveSettingsLocked(caller.getUserId());
- }
+ mDevicePolicyEngine.removeGlobalPolicy(
+ PolicyDefinition.MEMORY_TAGGING,
+ admin);
}
DevicePolicyEventLogger.createEvent(DevicePolicyEnums.SET_MTE_POLICY)
@@ -23817,10 +23850,6 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
Preconditions.checkCallAuthorization(isSystemUid(getCallerIdentity()),
"Only system services can call setMtePolicyBySystem");
- if (!Flags.setMtePolicyCoexistence()) {
- throw new UnsupportedOperationException("System can not set MTE policy only");
- }
-
EnforcingAdmin admin = EnforcingAdmin.createSystemEnforcingAdmin(systemEntity);
if (policy != DevicePolicyManager.MTE_NOT_CONTROLLED_BY_POLICY) {
mDevicePolicyEngine.setGlobalPolicy(
@@ -23858,31 +23887,16 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
@Override
public int getMtePolicy(String callerPackageName) {
final CallerIdentity caller = getCallerIdentity(callerPackageName);
- if (Flags.setMtePolicyCoexistence()) {
- enforcePermission(MANAGE_DEVICE_POLICY_MTE, caller.getPackageName(),
- UserHandle.USER_ALL);
- } else {
- Preconditions.checkCallAuthorization(
- isDefaultDeviceOwner(caller)
- || isProfileOwnerOfOrganizationOwnedDevice(caller)
- || isSystemUid(caller));
- }
+ enforcePermission(MANAGE_DEVICE_POLICY_MTE, caller.getPackageName(),
+ UserHandle.USER_ALL);
synchronized (getLockObject()) {
- if (Flags.setMtePolicyCoexistence()) {
- final EnforcingAdmin admin = enforcePermissionAndGetEnforcingAdmin(null,
- MANAGE_DEVICE_POLICY_MTE, callerPackageName, caller.getUserId());
- final Integer policyFromAdmin = mDevicePolicyEngine.getGlobalPolicySetByAdmin(
- PolicyDefinition.MEMORY_TAGGING, admin);
- return (policyFromAdmin != null ? policyFromAdmin
- : DevicePolicyManager.MTE_NOT_CONTROLLED_BY_POLICY);
- } else {
- ActiveAdmin admin =
- getDeviceOwnerOrProfileOwnerOfOrganizationOwnedDeviceLocked();
- return admin != null
- ? admin.mtePolicy
- : DevicePolicyManager.MTE_NOT_CONTROLLED_BY_POLICY;
- }
+ final EnforcingAdmin admin = enforcePermissionAndGetEnforcingAdmin(null,
+ MANAGE_DEVICE_POLICY_MTE, callerPackageName, caller.getUserId());
+ final Integer policyFromAdmin = mDevicePolicyEngine.getGlobalPolicySetByAdmin(
+ PolicyDefinition.MEMORY_TAGGING, admin);
+ return (policyFromAdmin != null ? policyFromAdmin
+ : DevicePolicyManager.MTE_NOT_CONTROLLED_BY_POLICY);
}
}
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/OwnersData.java b/services/devicepolicy/java/com/android/server/devicepolicy/OwnersData.java
index caaf0964bb4e..6dfe08c1eb7e 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/OwnersData.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/OwnersData.java
@@ -433,10 +433,8 @@ class OwnersData {
out.attributeBoolean(null, ATTR_RESET_PASSWORD_WITH_TOKEN_MIGRATED,
mResetPasswordWithTokenMigrated);
}
- if (Flags.setMtePolicyCoexistence()) {
- out.attributeBoolean(null, ATTR_MEMORY_TAGGING_MIGRATED,
- mMemoryTaggingMigrated);
- }
+ out.attributeBoolean(null, ATTR_MEMORY_TAGGING_MIGRATED,
+ mMemoryTaggingMigrated);
if (Flags.setKeyguardDisabledFeaturesCoexistence()) {
out.attributeBoolean(null, ATTR_SET_KEYGUARD_DISABLED_FEATURES_MIGRATED,
mSetKeyguardDisabledFeaturesMigrated);
@@ -514,8 +512,7 @@ class OwnersData {
mResetPasswordWithTokenMigrated = Flags.resetPasswordWithTokenCoexistence()
&& parser.getAttributeBoolean(null,
ATTR_RESET_PASSWORD_WITH_TOKEN_MIGRATED, false);
- mMemoryTaggingMigrated = Flags.setMtePolicyCoexistence()
- && parser.getAttributeBoolean(null,
+ mMemoryTaggingMigrated = parser.getAttributeBoolean(null,
ATTR_MEMORY_TAGGING_MIGRATED, false);
mSetKeyguardDisabledFeaturesMigrated =
Flags.setKeyguardDisabledFeaturesCoexistence()
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..de78271acddc 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,11 @@ public final class FoldableDeviceStateProvider implements DeviceStateProvider,
}
if (newState != INVALID_DEVICE_STATE_IDENTIFIER && newState != mLastReportedState) {
+ if (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/supervision/java/com/android/server/supervision/SupervisionService.java b/services/supervision/java/com/android/server/supervision/SupervisionService.java
index f731b50d81b4..bb6339c79502 100644
--- a/services/supervision/java/com/android/server/supervision/SupervisionService.java
+++ b/services/supervision/java/com/android/server/supervision/SupervisionService.java
@@ -64,6 +64,18 @@ import java.util.List;
public class SupervisionService extends ISupervisionManager.Stub {
private static final String LOG_TAG = "SupervisionService";
+ /**
+ * Activity action: Requests user confirmation of supervision credentials.
+ *
+ * <p>Use {@link Activity#startActivityForResult} to launch this activity. The result will be
+ * {@link Activity#RESULT_OK} if credentials are valid.
+ *
+ * <p>If supervision credentials are not configured, this action initiates the setup flow.
+ */
+ @VisibleForTesting
+ static final String ACTION_CONFIRM_SUPERVISION_CREDENTIALS =
+ "android.app.supervision.action.CONFIRM_SUPERVISION_CREDENTIALS";
+
// TODO(b/362756788): Does this need to be a LockGuard lock?
private final Object mLockDoNoUseDirectly = new Object();
@@ -81,25 +93,6 @@ public class SupervisionService extends ISupervisionManager.Stub {
}
/**
- * Creates an {@link Intent} that can be used with {@link Context#startActivity(Intent)} to
- * launch the activity to verify supervision credentials.
- *
- * <p>A valid {@link Intent} is always returned if supervision is enabled at the time this
- * method is called, the launched activity still need to perform validity checks as the
- * supervision state can change when it's launched. A null intent is returned if supervision is
- * disabled at the time of this method call.
- *
- * <p>A result code of {@link android.app.Activity#RESULT_OK} indicates successful verification
- * of the supervision credentials.
- */
- @Override
- @Nullable
- public Intent createConfirmSupervisionCredentialsIntent() {
- // TODO(b/392961554): Implement createAuthenticationIntent API
- throw new UnsupportedOperationException();
- }
-
- /**
* Returns whether supervision is enabled for the given user.
*
* <p>Supervision is automatically enabled when the supervision app becomes the profile owner or
@@ -139,6 +132,31 @@ public class SupervisionService extends ISupervisionManager.Stub {
}
}
+ /**
+ * Creates an {@link Intent} that can be used with {@link Context#startActivity(Intent)} to
+ * launch the activity to verify supervision credentials.
+ *
+ * <p>A valid {@link Intent} is always returned if supervision is enabled at the time this
+ * method is called, the launched activity still need to perform validity checks as the
+ * supervision state can change when it's launched. A null intent is returned if supervision is
+ * disabled at the time of this method call.
+ *
+ * <p>A result code of {@link android.app.Activity#RESULT_OK} indicates successful verification
+ * of the supervision credentials.
+ */
+ @Override
+ @Nullable
+ public Intent createConfirmSupervisionCredentialsIntent() {
+ // TODO(b/392961554): (1) Return null if supervision is not enabled.
+ // (2) check if PIN exists before return a valid intent.
+ enforceAnyPermission(QUERY_USERS, MANAGE_USERS);
+ final Intent intent = new Intent(ACTION_CONFIRM_SUPERVISION_CREDENTIALS);
+ // explicitly set the package for security
+ intent.setPackage("com.android.settings");
+
+ return intent;
+ }
+
@Override
public void onShellCommand(
@Nullable FileDescriptor in,
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/InputMethodSystemServerTests/src/com/android/inputmethodservice/AndroidTest.xml b/services/tests/InputMethodSystemServerTests/src/com/android/inputmethodservice/AndroidTest.xml
index 5fe5b237bdef..ea01fc4e6140 100644
--- a/services/tests/InputMethodSystemServerTests/src/com/android/inputmethodservice/AndroidTest.xml
+++ b/services/tests/InputMethodSystemServerTests/src/com/android/inputmethodservice/AndroidTest.xml
@@ -41,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/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/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/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/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/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/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/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/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..5d8f57866f7d 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,44 @@ public class MockingOomAdjusterTests {
@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/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/pm/UserManagerServiceTest.java b/services/tests/mockingservicestests/src/com/android/server/pm/UserManagerServiceMockedTest.java
index 6ad3df1dd6f2..ac11216bdf0a 100644
--- a/services/tests/mockingservicestests/src/com/android/server/pm/UserManagerServiceTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/pm/UserManagerServiceMockedTest.java
@@ -102,11 +102,12 @@ import java.io.FileInputStream;
import java.io.IOException;
/**
- * Run as {@code atest FrameworksMockingServicesTests:com.android.server.pm.UserManagerServiceTest}
+ * Run as {@code atest
+ * FrameworksMockingServicesTests:com.android.server.pm.UserManagerServiceMockedTest}
*/
-public final class UserManagerServiceTest {
+public final class UserManagerServiceMockedTest {
- private static final String TAG = UserManagerServiceTest.class.getSimpleName();
+ private static final String TAG = UserManagerServiceMockedTest.class.getSimpleName();
/**
* Id for a simple user (that doesn't have profiles).
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/mockingservicestests/src/com/android/server/wallpaper/WallpaperCropperTest.java b/services/tests/mockingservicestests/src/com/android/server/wallpaper/WallpaperCropperTest.java
index 49c37f163ff2..241ffdc19ce4 100644
--- a/services/tests/mockingservicestests/src/com/android/server/wallpaper/WallpaperCropperTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/wallpaper/WallpaperCropperTest.java
@@ -36,24 +36,29 @@ 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 +75,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,6 +89,12 @@ public class WallpaperCropperTest {
@Mock
private WallpaperDisplayHelper mWallpaperDisplayHelper;
+
+ @Mock
+ private WindowManager mWindowManager;
+
+ @Mock
+ private Resources mResources;
private WallpaperCropper mWallpaperCropper;
private static final Point PORTRAIT_ONE = new Point(500, 800);
@@ -175,14 +187,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 +211,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 +459,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 +479,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 +494,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 +504,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 +526,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 +537,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 +561,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 +598,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 +618,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 +640,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 +665,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 +682,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 +703,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 +722,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);
}
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/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/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/servicestests/src/com/android/server/accessibility/autoclick/AutoclickControllerTest.java b/services/tests/servicestests/src/com/android/server/accessibility/autoclick/AutoclickControllerTest.java
index 17d8882b487c..ea25e7992dd9 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
@@ -43,6 +43,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 +71,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);
@@ -571,6 +585,242 @@ public class AutoclickControllerTest {
assertThat(mController.mClickScheduler.getScheduledClickTimeForTesting()).isNotEqualTo(-1);
}
+ @Test
+ @EnableFlags(com.android.server.accessibility.Flags.FLAG_ENABLE_AUTOCLICK_INDICATOR)
+ public void pauseButton_panelNotHovered_clickNotTriggeredWhenPaused() {
+ injectFakeMouseActionHoverMoveEvent();
+
+ // Pause autoclick and ensure the panel is not hovered.
+ AutoclickTypePanel mockAutoclickTypePanel = mock(AutoclickTypePanel.class);
+ when(mockAutoclickTypePanel.isPaused()).thenReturn(true);
+ when(mockAutoclickTypePanel.isHovered()).thenReturn(false);
+ 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);
+
+ // Verify click is not triggered.
+ assertThat(mController.mClickScheduler.getIsActiveForTesting()).isFalse();
+ assertThat(mController.mClickScheduler.getScheduledClickTimeForTesting()).isEqualTo(-1);
+ }
+
+ @Test
+ @EnableFlags(com.android.server.accessibility.Flags.FLAG_ENABLE_AUTOCLICK_INDICATOR)
+ public void pauseButton_panelHovered_clickTriggeredWhenPaused() {
+ injectFakeMouseActionHoverMoveEvent();
+
+ // Pause autoclick and hover the panel.
+ AutoclickTypePanel mockAutoclickTypePanel = mock(AutoclickTypePanel.class);
+ when(mockAutoclickTypePanel.isPaused()).thenReturn(true);
+ 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);
+
+ // Verify click is triggered.
+ assertThat(mController.mClickScheduler.getIsActiveForTesting()).isTrue();
+ assertThat(mController.mClickScheduler.getScheduledClickTimeForTesting()).isNotEqualTo(-1);
+ }
+
+ @Test
+ @EnableFlags(com.android.server.accessibility.Flags.FLAG_ENABLE_AUTOCLICK_INDICATOR)
+ public void pauseButton_unhoveringCancelsClickWhenPaused() {
+ injectFakeMouseActionHoverMoveEvent();
+
+ // Pause autoclick and hover the panel.
+ AutoclickTypePanel mockAutoclickTypePanel = mock(AutoclickTypePanel.class);
+ when(mockAutoclickTypePanel.isPaused()).thenReturn(true);
+ 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);
+
+ // Verify click is triggered.
+ assertThat(mController.mClickScheduler.getIsActiveForTesting()).isTrue();
+ assertThat(mController.mClickScheduler.getScheduledClickTimeForTesting()).isNotEqualTo(-1);
+
+ // Now simulate the pointer being moved outside the panel.
+ when(mockAutoclickTypePanel.isHovered()).thenReturn(false);
+ mController.clickPanelController.onHoverChange(/* hovered= */ false);
+
+ // Verify pending click is canceled.
+ assertThat(mController.mClickScheduler.getIsActiveForTesting()).isFalse();
+ 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);
+
+ // 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 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/AutoclickLinearLayoutTest.java b/services/tests/servicestests/src/com/android/server/accessibility/autoclick/AutoclickLinearLayoutTest.java
new file mode 100644
index 000000000000..9e629f7c87a2
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/accessibility/autoclick/AutoclickLinearLayoutTest.java
@@ -0,0 +1,102 @@
+/*
+ * 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 android.testing.AndroidTestingRunner;
+import android.testing.TestableContext;
+import android.view.MotionEvent;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+/** Test cases for {@link AutoclickLinearLayout}. */
+@RunWith(AndroidTestingRunner.class)
+public class AutoclickLinearLayoutTest {
+ private boolean mHovered;
+
+ private final AutoclickLinearLayout.OnHoverChangedListener mListener =
+ new AutoclickLinearLayout.OnHoverChangedListener() {
+ @Override
+ public void onHoverChanged(boolean hovered) {
+ mHovered = hovered;
+ }
+ };
+
+ @Rule
+ public TestableContext mTestableContext =
+ new TestableContext(getInstrumentation().getContext());
+ private AutoclickLinearLayout mAutoclickLinearLayout;
+
+ @Before
+ public void setUp() {
+ mAutoclickLinearLayout = new AutoclickLinearLayout(mTestableContext);
+ }
+
+ @Test
+ public void autoclickLinearLayout_hoverChangedListener_setHovered() {
+ mHovered = false;
+ mAutoclickLinearLayout.setOnHoverChangedListener(mListener);
+ mAutoclickLinearLayout.onHoverChanged(/* hovered= */ true);
+ assertThat(mHovered).isTrue();
+ }
+
+ @Test
+ public void autoclickLinearLayout_hoverChangedListener_setNotHovered() {
+ mHovered = true;
+
+ mAutoclickLinearLayout.setOnHoverChangedListener(mListener);
+ mAutoclickLinearLayout.onHoverChanged(/* hovered= */ false);
+ assertThat(mHovered).isFalse();
+ }
+
+ @Test
+ public void autoclickLinearLayout_onInterceptHoverEvent_hovered() {
+ mAutoclickLinearLayout.setHovered(false);
+ mAutoclickLinearLayout.onInterceptHoverEvent(
+ getFakeMotionEvent(MotionEvent.ACTION_HOVER_ENTER));
+ assertThat(mAutoclickLinearLayout.isHovered()).isTrue();
+
+ mAutoclickLinearLayout.setHovered(false);
+ mAutoclickLinearLayout.onInterceptHoverEvent(
+ getFakeMotionEvent(MotionEvent.ACTION_HOVER_MOVE));
+ assertThat(mAutoclickLinearLayout.isHovered()).isTrue();
+ }
+
+ @Test
+ public void autoclickLinearLayout_onInterceptHoverEvent_hoveredExit() {
+ mAutoclickLinearLayout.setHovered(true);
+ mAutoclickLinearLayout.onInterceptHoverEvent(
+ getFakeMotionEvent(MotionEvent.ACTION_HOVER_EXIT));
+ assertThat(mAutoclickLinearLayout.isHovered()).isFalse();
+ }
+
+ private MotionEvent getFakeMotionEvent(int motionEventAction) {
+ return MotionEvent.obtain(
+ /* downTime= */ 0,
+ /* eventTime= */ 0,
+ /* action= */ motionEventAction,
+ /* x= */ 0,
+ /* y= */ 0,
+ /* metaState= */ 0);
+ }
+}
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..f445b50c7d9c
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/accessibility/autoclick/AutoclickScrollPanelTest.java
@@ -0,0 +1,92 @@
+/*
+ * 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.WindowManager;
+
+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;
+ private AutoclickScrollPanel mScrollPanel;
+
+ @Before
+ public void setUp() {
+ mTestableContext.addMockSystemService(Context.WINDOW_SERVICE, mMockWindowManager);
+ mScrollPanel = new AutoclickScrollPanel(mTestableContext, mMockWindowManager);
+ }
+
+ @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();
+ }
+}
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 9e123406dff5..dd089fcb1d70 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
@@ -78,6 +78,7 @@ public class AutoclickTypePanelTest {
private @AutoclickType int mActiveClickType = AUTOCLICK_TYPE_LEFT_CLICK;
private boolean mPaused;
+ private boolean mHovered;
private final ClickPanelControllerInterface clickPanelController =
new ClickPanelControllerInterface() {
@@ -90,6 +91,11 @@ public class AutoclickTypePanelTest {
public void toggleAutoclickPause(boolean paused) {
mPaused = paused;
}
+
+ @Override
+ public void onHoverChange(boolean hovered) {
+ mHovered = hovered;
+ }
};
@Before
@@ -195,11 +201,11 @@ public class AutoclickTypePanelTest {
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
@@ -264,7 +270,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,
@@ -273,7 +279,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);
}
@@ -287,7 +293,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,
@@ -296,7 +302,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);
}
@@ -313,7 +319,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.
@@ -347,7 +353,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);
}
@@ -386,7 +392,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);
}
@@ -412,6 +418,33 @@ public class AutoclickTypePanelTest {
upEvent.recycle();
}
+ @Test
+ public void hovered_IsHovered() {
+ AutoclickLinearLayout mContext = mAutoclickTypePanel.getContentViewForTesting();
+
+ assertThat(mAutoclickTypePanel.isHovered()).isFalse();
+ mContext.onInterceptHoverEvent(getFakeMotionHoverMoveEvent());
+ assertThat(mAutoclickTypePanel.isHovered()).isTrue();
+ }
+
+ @Test
+ public void hovered_OnHoverChange_isHovered() {
+ AutoclickLinearLayout mContext = mAutoclickTypePanel.getContentViewForTesting();
+
+ mHovered = false;
+ mContext.onHoverChanged(true);
+ assertThat(mHovered).isTrue();
+ }
+
+ @Test
+ public void hovered_OnHoverChange_isNotHovered() {
+ AutoclickLinearLayout mContext = mAutoclickTypePanel.getContentViewForTesting();
+
+ mHovered = true;
+ mContext.onHoverChanged(false);
+ assertThat(mHovered).isFalse();
+ }
+
private void verifyButtonHasSelectedStyle(@NonNull LinearLayout button) {
GradientDrawable gradientDrawable = (GradientDrawable) button.getBackground();
assertThat(gradientDrawable.getColor().getDefaultColor())
@@ -420,10 +453,20 @@ 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]);
assertThat(params.y).isEqualTo(expectedPosition[3]);
}
+
+ private MotionEvent getFakeMotionHoverMoveEvent() {
+ return MotionEvent.obtain(
+ /* downTime= */ 0,
+ /* eventTime= */ 0,
+ /* action= */ MotionEvent.ACTION_HOVER_MOVE,
+ /* x= */ 0,
+ /* y= */ 0,
+ /* metaState= */ 0);
+ }
}
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..4ea5fcfd79c8 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
@@ -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/appop/DiscreteAppOpSqlPersistenceTest.java b/services/tests/servicestests/src/com/android/server/appop/DiscreteAppOpSqlPersistenceTest.java
index 84713079c9d3..01fee7f66497 100644
--- a/services/tests/servicestests/src/com/android/server/appop/DiscreteAppOpSqlPersistenceTest.java
+++ b/services/tests/servicestests/src/com/android/server/appop/DiscreteAppOpSqlPersistenceTest.java
@@ -226,9 +226,9 @@ public class DiscreteAppOpSqlPersistenceTest {
mDiscreteRegistry.recordDiscreteAccess(event2);
}
- /** This clears in-memory cache and push records into the database. */
private void flushDiscreteOpsToDatabase() {
- mDiscreteRegistry.writeAndClearOldAccessHistory();
+ // This clears in-memory cache and push records from cache into the database.
+ mDiscreteRegistry.shutdown();
}
/**
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 21cc3bac3938..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,9 +104,10 @@ 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());
}
- sqlRegistry.writeAndClearOldAccessHistory();
+ // flush records from cache to the database.
+ sqlRegistry.shutdown();
assertThat(sqlRegistry.getAllDiscreteOps().size()).isEqualTo(RECORD_COUNT);
assertThat(sqlRegistry.getLargestAttributionChainId()).isEqualTo(RECORD_COUNT);
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..01bcc2584fe1 100644
--- a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java
+++ b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java
@@ -143,6 +143,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;
@@ -8715,6 +8716,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.
+ verifyZeroInteractions(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/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/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/location/contexthub/ContextHubEndpointTest.java b/services/tests/servicestests/src/com/android/server/location/contexthub/ContextHubEndpointTest.java
index 1de864cb4eb0..565a9b6c1c44 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,12 @@ 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 ContextHubClientManager mClientManager;
private ContextHubEndpointManager mEndpointManager;
private HubInfoRegistry mHubInfoRegistry;
@@ -229,23 +237,34 @@ 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);
+ }
+
/** 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 +277,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 +327,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/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/supervision/SupervisionServiceTest.kt b/services/tests/servicestests/src/com/android/server/supervision/SupervisionServiceTest.kt
index da022780ea75..fbf906586e8b 100644
--- a/services/tests/servicestests/src/com/android/server/supervision/SupervisionServiceTest.kt
+++ b/services/tests/servicestests/src/com/android/server/supervision/SupervisionServiceTest.kt
@@ -39,6 +39,7 @@ import com.android.internal.R
import com.android.server.LocalServices
import com.android.server.SystemService.TargetUser
import com.android.server.pm.UserManagerInternal
+import com.android.server.supervision.SupervisionService.ACTION_CONFIRM_SUPERVISION_CREDENTIALS
import com.google.common.truth.Truth.assertThat
import org.junit.Before
import org.junit.Rule
@@ -247,6 +248,13 @@ class SupervisionServiceTest {
assertThat(userData.supervisionLockScreenOptions).isNull()
}
+ @Test
+ fun createConfirmSupervisionCredentialsIntent() {
+ val intent = checkNotNull(service.createConfirmSupervisionCredentialsIntent())
+ assertThat(intent.action).isEqualTo(ACTION_CONFIRM_SUPERVISION_CREDENTIALS)
+ assertThat(intent.getPackage()).isEqualTo("com.android.settings")
+ }
+
private val systemSupervisionPackage: String
get() = context.getResources().getString(R.string.config_systemSupervision)
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 4a977be2aad9..a9759c8a61f3 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/GroupHelperTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/GroupHelperTest.java
@@ -66,6 +66,8 @@ import static org.mockito.Mockito.when;
import android.annotation.SuppressLint;
import android.app.Notification;
import android.app.NotificationChannel;
+import android.app.PendingIntent;
+import android.content.Intent;
import android.content.pm.PackageManager;
import android.graphics.Color;
import android.graphics.drawable.AdaptiveIconDrawable;
@@ -100,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.
@@ -620,6 +624,7 @@ public class GroupHelperTest extends UiServiceTestCase {
}
@Test
+ @EnableFlags(FLAG_NOTIFICATION_FORCE_GROUPING)
public void testAutoGrouped_singleOngoing_removeOngoingChild() {
final String pkg = "package";
@@ -639,7 +644,7 @@ public class GroupHelperTest extends UiServiceTestCase {
}
// remove ongoing
- mGroupHelper.onNotificationRemoved(notifications.get(0));
+ mGroupHelper.onNotificationRemoved(notifications.get(0), new ArrayList<>(), false);
// Summary is no longer ongoing
verify(mCallback).updateAutogroupSummary(anyInt(), anyString(), anyString(),
@@ -776,7 +781,7 @@ public class GroupHelperTest extends UiServiceTestCase {
}
// remove ongoing
- mGroupHelper.onNotificationRemoved(notifications.get(1));
+ mGroupHelper.onNotificationRemoved(notifications.get(1), new ArrayList<>(), false);
// Summary is still ongoing
verify(mCallback, times(1)).updateAutogroupSummary(anyInt(), anyString(), anyString(),
@@ -936,7 +941,7 @@ public class GroupHelperTest extends UiServiceTestCase {
mGroupHelper.onNotificationPosted(r, false);
}
- mGroupHelper.onNotificationRemoved(notifications.get(0));
+ mGroupHelper.onNotificationRemoved(notifications.get(0), new ArrayList<>(), false);
// Summary should still be autocancelable
verify(mCallback, times(1)).updateAutogroupSummary(anyInt(), anyString(), anyString(),
@@ -944,6 +949,7 @@ public class GroupHelperTest extends UiServiceTestCase {
}
@Test
+ @EnableFlags(FLAG_NOTIFICATION_FORCE_GROUPING)
@DisableFlags(android.app.Flags.FLAG_CHECK_AUTOGROUP_BEFORE_POST)
public void testDropToZeroRemoveGroup_disableFlag() {
final String pkg = "package";
@@ -963,19 +969,20 @@ public class GroupHelperTest extends UiServiceTestCase {
Mockito.reset(mCallback);
for (int i = 0; i < AUTOGROUP_AT_COUNT - 1; i++) {
- mGroupHelper.onNotificationRemoved(posted.remove(0));
+ mGroupHelper.onNotificationRemoved(posted.remove(0), new ArrayList<>(), false);
}
verify(mCallback, never()).removeAutoGroup(anyString());
verify(mCallback, never()).removeAutoGroupSummary(anyInt(), anyString(), anyString());
Mockito.reset(mCallback);
- mGroupHelper.onNotificationRemoved(posted.remove(0));
+ mGroupHelper.onNotificationRemoved(posted.remove(0), new ArrayList<>(), false);
verify(mCallback, never()).removeAutoGroup(anyString());
verify(mCallback, times(1)).removeAutoGroupSummary(anyInt(), anyString(), anyString());
}
@Test
- @EnableFlags(android.app.Flags.FLAG_CHECK_AUTOGROUP_BEFORE_POST)
+ @EnableFlags({FLAG_NOTIFICATION_FORCE_GROUPING,
+ android.app.Flags.FLAG_CHECK_AUTOGROUP_BEFORE_POST})
public void testDropToZeroRemoveGroup() {
final String pkg = "package";
ArrayList<NotificationRecord> posted = new ArrayList<>();
@@ -994,13 +1001,13 @@ public class GroupHelperTest extends UiServiceTestCase {
Mockito.reset(mCallback);
for (int i = 0; i < AUTOGROUP_AT_COUNT - 1; i++) {
- mGroupHelper.onNotificationRemoved(posted.remove(0));
+ mGroupHelper.onNotificationRemoved(posted.remove(0), new ArrayList<>(), false);
}
verify(mCallback, never()).removeAutoGroup(anyString());
verify(mCallback, never()).removeAutoGroupSummary(anyInt(), anyString(), anyString());
Mockito.reset(mCallback);
- mGroupHelper.onNotificationRemoved(posted.remove(0));
+ mGroupHelper.onNotificationRemoved(posted.remove(0), new ArrayList<>(), false);
verify(mCallback, never()).removeAutoGroup(anyString());
verify(mCallback, times(1)).removeAutoGroupSummary(anyInt(), anyString(), anyString());
}
@@ -1072,6 +1079,7 @@ public class GroupHelperTest extends UiServiceTestCase {
}
@Test
+ @EnableFlags(FLAG_NOTIFICATION_FORCE_GROUPING)
@DisableFlags(android.app.Flags.FLAG_CHECK_AUTOGROUP_BEFORE_POST)
public void testNewNotificationsAddedToAutogroup_ifOriginalNotificationsCanceled_alwaysGroup() {
final String pkg = "package";
@@ -1091,7 +1099,7 @@ public class GroupHelperTest extends UiServiceTestCase {
Mockito.reset(mCallback);
for (int i = posted.size() - 2; i >= 0; i--) {
- mGroupHelper.onNotificationRemoved(posted.remove(i));
+ mGroupHelper.onNotificationRemoved(posted.remove(i), new ArrayList<>(), false);
}
verify(mCallback, never()).removeAutoGroup(anyString());
verify(mCallback, never()).removeAutoGroupSummary(anyInt(), anyString(), anyString());
@@ -1114,7 +1122,8 @@ public class GroupHelperTest extends UiServiceTestCase {
}
@Test
- @EnableFlags(android.app.Flags.FLAG_CHECK_AUTOGROUP_BEFORE_POST)
+ @EnableFlags({FLAG_NOTIFICATION_FORCE_GROUPING,
+ android.app.Flags.FLAG_CHECK_AUTOGROUP_BEFORE_POST})
public void testNewNotificationsAddedToAutogroup_ifOriginalNotificationsCanceled() {
final String pkg = "package";
ArrayList<NotificationRecord> posted = new ArrayList<>();
@@ -1134,7 +1143,7 @@ public class GroupHelperTest extends UiServiceTestCase {
Mockito.reset(mCallback);
for (int i = posted.size() - 2; i >= 0; i--) {
- mGroupHelper.onNotificationRemoved(posted.remove(i));
+ mGroupHelper.onNotificationRemoved(posted.remove(i), new ArrayList<>(), false);
}
verify(mCallback, never()).removeAutoGroup(anyString());
verify(mCallback, never()).removeAutoGroupSummary(anyInt(), anyString(), anyString());
@@ -1481,7 +1490,7 @@ public class GroupHelperTest extends UiServiceTestCase {
}
// Remove last notification (the only one with different icon and color)
- mGroupHelper.onNotificationRemoved(notifications.get(lastIdx));
+ mGroupHelper.onNotificationRemoved(notifications.get(lastIdx), new ArrayList<>(), false);
// Summary should be updated to the common icon and color
verify(mCallback, times(1)).updateAutogroupSummary(anyInt(), anyString(), anyString(),
@@ -2162,7 +2171,7 @@ public class GroupHelperTest extends UiServiceTestCase {
NotificationRecord r = getNotificationRecord(pkg, i, String.valueOf(i),
UserHandle.SYSTEM, "testGrp " + i, false);
r.setOverrideGroupKey(expectedGroupKey);
- mGroupHelper.onNotificationRemoved(r, notificationList);
+ mGroupHelper.onNotificationRemoved(r, notificationList, false);
}
// Check that the autogroup summary is removed
verify(mCallback, times(1)).removeAutoGroupSummary(anyInt(), eq(pkg),
@@ -2206,7 +2215,7 @@ public class GroupHelperTest extends UiServiceTestCase {
Mockito.reset(mCallback);
for (NotificationRecord r: childrenToRemove) {
notificationList.remove(r);
- mGroupHelper.onNotificationRemoved(r, notificationList);
+ mGroupHelper.onNotificationRemoved(r, notificationList, false);
}
// Only call onGroupedNotificationRemovedWithDelay with the summary notification
mGroupHelper.onGroupedNotificationRemovedWithDelay(summary, notificationList,
@@ -2270,7 +2279,7 @@ public class GroupHelperTest extends UiServiceTestCase {
Mockito.reset(mCallback);
for (NotificationRecord r: childrenToRemove) {
notificationList.remove(r);
- mGroupHelper.onNotificationRemoved(r, notificationList);
+ mGroupHelper.onNotificationRemoved(r, notificationList, false);
}
// Only call onGroupedNotificationRemovedWithDelay with the summary notification
mGroupHelper.onGroupedNotificationRemovedWithDelay(summary, notificationList,
@@ -2327,7 +2336,7 @@ public class GroupHelperTest extends UiServiceTestCase {
for (NotificationRecord r: notificationList) {
if (r.getGroupKey().contains(groupToRemove)) {
r.isCanceled = true;
- mGroupHelper.onNotificationRemoved(r, notificationList);
+ mGroupHelper.onNotificationRemoved(r, notificationList, false);
}
}
// Only call onGroupedNotificationRemovedWithDelay with the summary notification
@@ -2452,7 +2461,7 @@ public class GroupHelperTest extends UiServiceTestCase {
true);
assertThat(GroupHelper.getSection(notifToInvalidate)).isNull();
notificationList.remove(notifToInvalidate);
- mGroupHelper.onNotificationRemoved(notifToInvalidate, notificationList);
+ mGroupHelper.onNotificationRemoved(notifToInvalidate, notificationList, false);
// Check that the autogroup was updated
verify(mCallback, never()).removeAutoGroup(anyString());
@@ -4023,7 +4032,7 @@ public class GroupHelperTest extends UiServiceTestCase {
//Cancel child 0 => remove cached summary
childToRemove.isCanceled = true;
notificationListAfterGrouping.remove(childToRemove);
- mGroupHelper.onNotificationRemoved(childToRemove, notificationListAfterGrouping);
+ mGroupHelper.onNotificationRemoved(childToRemove, notificationListAfterGrouping, false);
CachedSummary cachedSummary = mGroupHelper.findCanceledSummary(pkg, String.valueOf(id), id,
UserHandle.SYSTEM.getIdentifier());
assertThat(cachedSummary).isNull();
@@ -4399,4 +4408,134 @@ public class GroupHelperTest extends UiServiceTestCase {
"PeopleSection(priority)");
}
+ @Test
+ @EnableFlags(FLAG_NOTIFICATION_FORCE_GROUPING)
+ public void onNotificationRemoved_lastChildOfCachedSummary_firesCachedSummaryDeleteIntent() {
+ final List<NotificationRecord> notificationList = new ArrayList<>();
+ final ArrayMap<String, NotificationRecord> summaryByGroup = new ArrayMap<>();
+ final String pkg = "package";
+ NotificationRecord onlyChildOfFirstGroup = null;
+ PendingIntent deleteIntentofFirstSummary = PendingIntent.getActivity(mContext, 1,
+ new Intent(), PendingIntent.FLAG_IMMUTABLE);
+ // Post singleton groups, above forced group limit, so they are force grouped
+ for (int i = 0; i < AUTOGROUP_AT_COUNT; i++) {
+ NotificationRecord summary = getNotificationRecord(pkg, i,
+ String.valueOf(i), UserHandle.SYSTEM, "testGrp " + i, true);
+ notificationList.add(summary);
+ NotificationRecord child = getNotificationRecord(pkg, i + 42,
+ String.valueOf(i + 42), UserHandle.SYSTEM, "testGrp " + i, false);
+ notificationList.add(child);
+ summaryByGroup.put(summary.getGroupKey(), summary);
+ if (i == 0) {
+ onlyChildOfFirstGroup = child;
+ summary.getNotification().deleteIntent = deleteIntentofFirstSummary;
+ }
+ mGroupHelper.onNotificationPostedWithDelay(child, notificationList, summaryByGroup);
+ summary.isCanceled = true; // simulate removing the app summary
+ mGroupHelper.onNotificationPostedWithDelay(summary, notificationList, summaryByGroup);
+ }
+ // Sparse group autogrouping would've removed the summary.
+ notificationList.remove(0);
+
+ // Now remove the only child of the first (force-grouped, cuz sparse) group.
+ notificationList.remove(0);
+ onlyChildOfFirstGroup.isCanceled = true;
+ mGroupHelper.onNotificationRemoved(onlyChildOfFirstGroup, notificationList, true);
+
+ 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
+ verifyZeroInteractions(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/NotificationManagerServiceTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
index 37ab541f12da..dd5c601619a0 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
@@ -349,6 +349,7 @@ import com.android.server.wm.WindowManagerInternal;
import com.google.android.collect.Lists;
import com.google.common.collect.ImmutableList;
+import com.google.common.collect.Iterables;
import libcore.junit.util.compat.CoreCompatChangeRule.DisableCompatChanges;
import libcore.junit.util.compat.CoreCompatChangeRule.EnableCompatChanges;
@@ -2788,8 +2789,8 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
nr1.getSbn().getId(), nr1.getSbn().getUserId());
waitForIdle();
- verify(mGroupHelper, times(1)).onNotificationRemoved(eq(nr0), any());
- verify(mGroupHelper, times(1)).onNotificationRemoved(eq(nr1), any());
+ verify(mGroupHelper, times(1)).onNotificationRemoved(eq(nr0), any(), eq(false));
+ verify(mGroupHelper, times(1)).onNotificationRemoved(eq(nr1), any(), eq(false));
// GroupHelper would send 'remove summary' event
mService.clearAutogroupSummaryLocked(nr1.getUserId(), nr1.getSbn().getPackageName(),
@@ -3155,8 +3156,8 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
waitForIdle();
// Check that onGroupedNotificationRemovedWithDelay was called only once
- verify(mGroupHelper, times(1)).onNotificationRemoved(eq(r1), any());
- verify(mGroupHelper, times(1)).onNotificationRemoved(eq(r2), any());
+ verify(mGroupHelper, times(1)).onNotificationRemoved(eq(r1), any(), eq(false));
+ verify(mGroupHelper, times(1)).onNotificationRemoved(eq(r2), any(), eq(false));
verify(mGroupHelper, times(1)).onGroupedNotificationRemovedWithDelay(eq(summary), any(),
any());
}
@@ -3201,9 +3202,9 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
waitForIdle();
// Check that onGroupedNotificationRemovedWithDelay was never called: summary was canceled
- verify(mGroupHelper, times(1)).onNotificationRemoved(eq(r1), any());
- verify(mGroupHelper, times(1)).onNotificationRemoved(eq(r2), any());
- verify(mGroupHelper, times(1)).onNotificationRemoved(eq(summary), any());
+ verify(mGroupHelper, times(1)).onNotificationRemoved(eq(r1), any(), eq(false));
+ verify(mGroupHelper, times(1)).onNotificationRemoved(eq(r2), any(), eq(false));
+ verify(mGroupHelper, times(1)).onNotificationRemoved(eq(summary), any(), eq(false));
verify(mGroupHelper, never()).onGroupedNotificationRemovedWithDelay(any(), any(), any());
}
@@ -5210,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))
@@ -14122,9 +14088,10 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
waitForIdle();
// Check that child notifications are also removed
- verify(mGroupHelper, times(1)).onNotificationRemoved(eq(aggregateSummary), any());
- verify(mGroupHelper, times(1)).onNotificationRemoved(eq(nr0), any());
- verify(mGroupHelper, times(1)).onNotificationRemoved(eq(nr1), any());
+ verify(mGroupHelper, times(1)).onNotificationRemoved(eq(aggregateSummary), any(),
+ eq(false));
+ verify(mGroupHelper, times(1)).onNotificationRemoved(eq(nr0), any(), eq(false));
+ verify(mGroupHelper, times(1)).onNotificationRemoved(eq(nr1), any(), eq(false));
// Make sure the summary was removed and not re-posted
assertThat(mService.getNotificationRecordCount()).isEqualTo(0);
@@ -18659,4 +18626,35 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
}
}
+ @Test
+ @EnableFlags(FLAG_NOTIFICATION_FORCE_GROUPING)
+ public void clearAll_fromUser_willSendDeleteIntentForCachedSummaries() throws Exception {
+ NotificationRecord n = generateNotificationRecord(
+ mTestNotificationChannel, 1, "group", true);
+ mBinderService.enqueueNotificationWithTag(mPkg, mPkg, "tag",
+ n.getSbn().getId(), n.getSbn().getNotification(), n.getSbn().getUserId());
+ waitForIdle();
+ n = Iterables.getOnlyElement(mService.mNotificationList);
+
+ mService.mNotificationDelegate.onClearAll(mUid, Binder.getCallingPid(), n.getUserId());
+ waitForIdle();
+
+ verify(mGroupHelper).onNotificationRemoved(eq(n), any(), eq(true));
+ }
+
+ @Test
+ @EnableFlags(FLAG_NOTIFICATION_FORCE_GROUPING)
+ public void cancel_fromApp_willNotSendDeleteIntentForCachedSummaries() throws Exception {
+ NotificationRecord n = generateNotificationRecord(
+ mTestNotificationChannel, 1, "group", true);
+ mBinderService.enqueueNotificationWithTag(mPkg, mPkg, "tag",
+ n.getSbn().getId(), n.getSbn().getNotification(), n.getSbn().getUserId());
+ waitForIdle();
+ n = Iterables.getOnlyElement(mService.mNotificationList);
+
+ mBinderService.cancelAllNotifications(mPkg, mUserId);
+ waitForIdle();
+
+ verify(mGroupHelper).onNotificationRemoved(eq(n), any(), eq(false));
+ }
}
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 5dea44d6ebf4..a02f628ce9b7 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,7 +91,6 @@ 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;
@@ -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"
@@ -1219,7 +1217,8 @@ public class PreferencesHelperTest extends UiServiceTestCase {
ByteArrayOutputStream baos = writeXmlAndPurge(
PKG_N_MR1, UID_N_MR1, false, USER_SYSTEM);
- String expected = "<ranking version=\"4\">\n"
+ String expected = "<ranking version=\"4\" "
+ + "last_bubbles_version_upgrade=\"" + Build.VERSION.SDK_INT + "\">\n"
+ "<package name=\"com.example.o\" show_badge=\"true\" "
+ "app_user_locked_fields=\"0\" sent_invalid_msg=\"false\" "
+ "sent_valid_msg=\"false\" user_demote_msg_app=\"false\" sent_valid_bubble"
@@ -1303,7 +1302,8 @@ public class PreferencesHelperTest extends UiServiceTestCase {
ByteArrayOutputStream baos = writeXmlAndPurge(
PKG_N_MR1, UID_N_MR1, true, USER_SYSTEM);
- String expected = "<ranking version=\"4\">\n"
+ String expected = "<ranking version=\"4\" "
+ + "last_bubbles_version_upgrade=\"" + Build.VERSION.SDK_INT + "\">\n"
// Importance 0 because off in permissionhelper
+ "<package name=\"com.example.o\" importance=\"0\" show_badge=\"true\" "
+ "app_user_locked_fields=\"0\" sent_invalid_msg=\"false\" "
@@ -1389,7 +1389,8 @@ public class PreferencesHelperTest extends UiServiceTestCase {
ByteArrayOutputStream baos = writeXmlAndPurge(
PKG_N_MR1, UID_N_MR1, true, USER_SYSTEM);
- String expected = "<ranking version=\"4\">\n"
+ String expected = "<ranking version=\"4\" "
+ + "last_bubbles_version_upgrade=\"" + Build.VERSION.SDK_INT + "\">\n"
// Importance 0 because off in permissionhelper
+ "<package name=\"com.example.o\" importance=\"0\" show_badge=\"true\" "
+ "app_user_locked_fields=\"0\" sent_invalid_msg=\"false\" "
@@ -1440,7 +1441,8 @@ public class PreferencesHelperTest extends UiServiceTestCase {
ByteArrayOutputStream baos = writeXmlAndPurge(
PKG_N_MR1, UID_N_MR1, true, USER_SYSTEM);
- String expected = "<ranking version=\"4\">\n"
+ String expected = "<ranking version=\"4\" "
+ + "last_bubbles_version_upgrade=\"" + Build.VERSION.SDK_INT + "\">\n"
// Packages that exist solely in permissionhelper
+ "<package name=\"" + PKG_P + "\" importance=\"3\" />\n"
+ "<package name=\"" + PKG_O + "\" importance=\"0\" />\n"
@@ -1651,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
@@ -1706,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
@@ -1784,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);
@@ -3259,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);
@@ -4474,7 +4421,7 @@ public class PreferencesHelperTest extends UiServiceTestCase {
}
@Test
- public void testBubblePreference_upgradeWithSAWPermission() throws Exception {
+ public void testBubblePreference_noLastVersionWithSAWPermission() throws Exception {
when(mAppOpsManager.noteOpNoThrow(eq(OP_SYSTEM_ALERT_WINDOW), anyInt(),
anyString(), eq(null), anyString())).thenReturn(MODE_ALLOWED);
@@ -4495,6 +4442,51 @@ public class PreferencesHelperTest extends UiServiceTestCase {
}
@Test
+ public void testBubblePreference_differentLastVersionWithSAWPermission() throws Exception {
+ when(mAppOpsManager.noteOpNoThrow(eq(OP_SYSTEM_ALERT_WINDOW), anyInt(),
+ anyString(), eq(null), anyString())).thenReturn(MODE_ALLOWED);
+ final String xml = "<ranking version=\"4\" last_bubbles_version_upgrade=\"34\">\n"
+ + "<package name=\"" + PKG_O + "\" uid=\"" + UID_O + "\">\n"
+ + "<channel id=\"someId\" name=\"hi\""
+ + " importance=\"3\"/>"
+ + "</package>"
+ + "</ranking>";
+ TypedXmlPullParser parser = Xml.newFastPullParser();
+ parser.setInput(new BufferedInputStream(new ByteArrayInputStream(xml.getBytes())),
+ null);
+ parser.nextTag();
+ mHelper.readXml(parser, false, UserHandle.USER_ALL);
+
+ assertEquals(BUBBLE_PREFERENCE_ALL, mHelper.getBubblePreference(PKG_O, UID_O));
+ assertEquals(0, mHelper.getAppLockedFields(PKG_O, UID_O));
+ }
+
+ @Test
+ public void testBubblePreference_sameLastVersionWithSAWPermission() throws Exception {
+ when(mAppOpsManager.noteOpNoThrow(eq(OP_SYSTEM_ALERT_WINDOW), anyInt(),
+ anyString(), eq(null), anyString())).thenReturn(MODE_ALLOWED);
+
+ final String xml = "<ranking version=\"4\" "
+ + "last_bubbles_version_upgrade=\"" + Build.VERSION.SDK_INT + "\">\n"
+ + "<package name=\"" + PKG_O + "\" uid=\"" + UID_O + "\">\n"
+ + "<channel id=\"someId\" name=\"hi\""
+ + " importance=\"3\"/>"
+ + "</package>"
+ + "</ranking>";
+ TypedXmlPullParser parser = Xml.newFastPullParser();
+ parser.setInput(new BufferedInputStream(new ByteArrayInputStream(xml.getBytes())),
+ null);
+ parser.nextTag();
+ mHelper.readXml(parser, false, UserHandle.USER_ALL);
+
+ assertEquals(DEFAULT_BUBBLE_PREFERENCE, mHelper.getBubblePreference(PKG_O, UID_O));
+ assertEquals(0, mHelper.getAppLockedFields(PKG_O, UID_O));
+ // Version was the same SAW check should not have happened
+ verify(mAppOpsManager, never()).noteOpNoThrow(eq(OP_SYSTEM_ALERT_WINDOW), anyInt(),
+ anyString(), eq(null), anyString());
+ }
+
+ @Test
public void testBubblePreference_upgradeWithSAWThenUserOverride() throws Exception {
when(mAppOpsManager.noteOpNoThrow(eq(OP_SYSTEM_ALERT_WINDOW), anyInt(),
anyString(), eq(null), anyString())).thenReturn(MODE_ALLOWED);
@@ -7037,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/ZenModeHelperTest.java b/services/tests/uiservicestests/src/com/android/server/notification/ZenModeHelperTest.java
index 51891ef71bbd..5377102e5a13 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/ZenModeHelperTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/ZenModeHelperTest.java
@@ -203,6 +203,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;
@@ -2478,6 +2479,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
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/wmtests/src/com/android/server/policy/KeyGestureEventTests.java b/services/tests/wmtests/src/com/android/server/policy/KeyGestureEventTests.java
index 6d8a48799112..3ca019728c2b 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},
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 301a7544227f..8992c2e632b2 100644
--- a/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java
@@ -32,6 +32,7 @@ import static android.content.pm.ActivityInfo.LOCK_TASK_LAUNCH_MODE_ALWAYS;
import static android.content.pm.ActivityInfo.LOCK_TASK_LAUNCH_MODE_DEFAULT;
import static android.content.pm.ActivityInfo.LOCK_TASK_LAUNCH_MODE_IF_ALLOWLISTED;
import static android.content.pm.ActivityInfo.LOCK_TASK_LAUNCH_MODE_NEVER;
+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.RESIZE_MODE_UNRESIZEABLE;
@@ -41,8 +42,8 @@ import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_PORTRAIT;
import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_REVERSE_LANDSCAPE;
import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_UNSET;
import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED;
-import static android.content.pm.ApplicationInfo.CATEGORY_SOCIAL;
import static android.content.pm.ApplicationInfo.CATEGORY_GAME;
+import static android.content.pm.ApplicationInfo.CATEGORY_SOCIAL;
import static android.content.res.Configuration.ORIENTATION_LANDSCAPE;
import static android.content.res.Configuration.ORIENTATION_PORTRAIT;
import static android.content.res.Configuration.UI_MODE_TYPE_DESK;
@@ -66,7 +67,6 @@ import static com.android.dx.mockito.inline.extended.ExtendedMockito.anyBoolean;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.atLeast;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.doAnswer;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.doCallRealMethod;
-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.eq;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.mock;
@@ -134,6 +134,7 @@ import android.os.Bundle;
import android.os.PersistableBundle;
import android.os.Process;
import android.os.RemoteException;
+import android.platform.test.annotations.DisableFlags;
import android.platform.test.annotations.EnableFlags;
import android.platform.test.annotations.Presubmit;
import android.provider.DeviceConfig;
@@ -196,7 +197,7 @@ public class ActivityRecordTests extends WindowTestsBase {
// Because the booted state is set, avoid starting real home if there is no task.
doReturn(false).when(mRootWindowContainer).resumeHomeActivity(any(), anyString(), any());
// Do not execute the transaction, because we can't verify the parameter after it recycles.
- doNothing().when(mClientLifecycleManager).scheduleTransaction(any());
+ doReturn(true).when(mClientLifecycleManager).scheduleTransaction(any());
}
private TestStartingWindowOrganizer registerTestStartingWindowOrganizer() {
@@ -266,7 +267,7 @@ public class ActivityRecordTests extends WindowTestsBase {
break;
}
}
- return null;
+ return true;
}).when(mClientLifecycleManager).scheduleTransaction(any());
activity.setState(STOPPED, "testPausingWhenVisibleFromStopped");
@@ -723,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(
@@ -737,6 +739,8 @@ 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)
public void testOrientation_dontAllowFixedOrientationForCameraCompatFreeformIfNotEnabled() {
final ActivityRecord activity = setupDisplayAndActivityForCameraCompat(
/* isCameraRunning= */ true, WINDOWING_MODE_FREEFORM);
@@ -750,6 +754,22 @@ public class ActivityRecordTests extends WindowTestsBase {
}
@Test
+ @EnableFlags({Flags.FLAG_ENABLE_CAMERA_COMPAT_FOR_DESKTOP_WINDOWING,
+ Flags.FLAG_ENABLE_CAMERA_COMPAT_FOR_DESKTOP_WINDOWING_OPT_OUT})
+ @EnableCompatChanges({OVERRIDE_CAMERA_COMPAT_DISABLE_SIMULATE_REQUESTED_ORIENTATION})
+ public void testOrientation_dontAllowFixedOrientationForCameraCompatFreeformIfOptedOut() {
+ final ActivityRecord activity = setupDisplayAndActivityForCameraCompat(
+ /* isCameraRunning= */ true, WINDOWING_MODE_FREEFORM);
+
+ // Task in landscape.
+ assertEquals(ORIENTATION_LANDSCAPE, activity.getTask().getConfiguration().orientation);
+ // Activity is not letterboxed.
+ assertEquals(ORIENTATION_LANDSCAPE, activity.getConfiguration().orientation);
+ assertFalse(activity.mAppCompatController.getAspectRatioPolicy()
+ .isLetterboxedForFixedOrientationAndAspectRatio());
+ }
+
+ @Test
@EnableFlags(Flags.FLAG_ENABLE_CAMERA_COMPAT_FOR_DESKTOP_WINDOWING)
@EnableCompatChanges({OVERRIDE_CAMERA_COMPAT_ENABLE_FREEFORM_WINDOWING_TREATMENT})
public void testOrientation_noFixedOrientationForCameraCompatFreeformIfCameraNotRunning() {
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..86d901b640ff 100644
--- a/services/tests/wmtests/src/com/android/server/wm/ActivityTaskSupervisorTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/ActivityTaskSupervisorTests.java
@@ -488,14 +488,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/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/BackgroundActivityStartControllerExemptionTests.java b/services/tests/wmtests/src/com/android/server/wm/BackgroundActivityStartControllerExemptionTests.java
index c934c55dfb8e..e3a8776aca6a 100644
--- a/services/tests/wmtests/src/com/android/server/wm/BackgroundActivityStartControllerExemptionTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/BackgroundActivityStartControllerExemptionTests.java
@@ -486,7 +486,7 @@ public class BackgroundActivityStartControllerExemptionTests {
// setup state
when(mCallerApp.areBackgroundActivityStartsAllowed(anyInt(), any())).thenReturn(
- new BalVerdict(BAL_ALLOW_FOREGROUND, false, "allowed"));
+ new BalVerdict(BAL_ALLOW_FOREGROUND, "allowed"));
when(mService.getBalAppSwitchesState()).thenReturn(APP_SWITCH_ALLOW);
// prepare call
@@ -523,7 +523,7 @@ public class BackgroundActivityStartControllerExemptionTests {
mCallerApp);
when(mService.getBalAppSwitchesState()).thenReturn(APP_SWITCH_ALLOW);
when(mCallerApp.areBackgroundActivityStartsAllowed(anyInt(), any())).thenReturn(
- new BalVerdict(BAL_ALLOW_FOREGROUND, false, "allowed"));
+ new BalVerdict(BAL_ALLOW_FOREGROUND, "allowed"));
// prepare call
PendingIntentRecord originatingPendingIntent = mPendingIntentRecord;
@@ -572,7 +572,7 @@ public class BackgroundActivityStartControllerExemptionTests {
when(mCallerApp.areBackgroundActivityStartsAllowed(anyInt(), any())).thenReturn(
BalVerdict.BLOCK);
when(otherProcess.areBackgroundActivityStartsAllowed(anyInt(), any())).thenReturn(
- new BalVerdict(BAL_ALLOW_FOREGROUND, false, "allowed"));
+ new BalVerdict(BAL_ALLOW_FOREGROUND, "allowed"));
// prepare call
PendingIntentRecord originatingPendingIntent = mPendingIntentRecord;
diff --git a/services/tests/wmtests/src/com/android/server/wm/BackgroundActivityStartControllerLogTests.java b/services/tests/wmtests/src/com/android/server/wm/BackgroundActivityStartControllerLogTests.java
index 99e730ae76cf..cd5f3912bfc6 100644
--- a/services/tests/wmtests/src/com/android/server/wm/BackgroundActivityStartControllerLogTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/BackgroundActivityStartControllerLogTests.java
@@ -92,7 +92,7 @@ public class BackgroundActivityStartControllerLogTests {
@Test
public void intent_visible_noLog() {
useIntent();
- BalVerdict finalVerdict = new BalVerdict(BAL_ALLOW_VISIBLE_WINDOW, false, "visible");
+ BalVerdict finalVerdict = new BalVerdict(BAL_ALLOW_VISIBLE_WINDOW, "visible");
mState.setResultForCaller(finalVerdict);
mState.setResultForRealCaller(BalVerdict.BLOCK);
assertThat(mController.shouldLogStats(finalVerdict, mState)).isFalse();
@@ -101,7 +101,7 @@ public class BackgroundActivityStartControllerLogTests {
@Test
public void intent_saw_log() {
useIntent();
- BalVerdict finalVerdict = new BalVerdict(BAL_ALLOW_SAW_PERMISSION, false, "SAW");
+ BalVerdict finalVerdict = new BalVerdict(BAL_ALLOW_SAW_PERMISSION, "SAW");
mState.setResultForCaller(finalVerdict);
mState.setResultForRealCaller(BalVerdict.BLOCK);
assertThat(mController.shouldLogStats(finalVerdict, mState)).isTrue();
@@ -111,7 +111,7 @@ public class BackgroundActivityStartControllerLogTests {
@Test
public void pendingIntent_callerOnly_saw_log() {
usePendingIntent();
- BalVerdict finalVerdict = new BalVerdict(BAL_ALLOW_SAW_PERMISSION, false, "SAW");
+ BalVerdict finalVerdict = new BalVerdict(BAL_ALLOW_SAW_PERMISSION, "SAW");
mState.setResultForCaller(finalVerdict);
mState.setResultForRealCaller(BalVerdict.BLOCK);
assertThat(mController.shouldLogStats(finalVerdict, mState)).isTrue();
@@ -121,7 +121,7 @@ public class BackgroundActivityStartControllerLogTests {
@Test
public void pendingIntent_realCallerOnly_saw_log() {
usePendingIntent();
- BalVerdict finalVerdict = new BalVerdict(BAL_ALLOW_SAW_PERMISSION, false, "SAW")
+ BalVerdict finalVerdict = new BalVerdict(BAL_ALLOW_SAW_PERMISSION, "SAW")
.setBasedOnRealCaller();
mState.setResultForCaller(BalVerdict.BLOCK);
mState.setResultForRealCaller(finalVerdict);
@@ -131,7 +131,7 @@ public class BackgroundActivityStartControllerLogTests {
@Test
public void intent_shouldLogIntentActivity() {
- BalVerdict finalVerdict = new BalVerdict(BAL_ALLOW_SAW_PERMISSION, false, "SAW");
+ BalVerdict finalVerdict = new BalVerdict(BAL_ALLOW_SAW_PERMISSION, "SAW");
useIntent(APP1_UID);
assertThat(mController.shouldLogIntentActivity(finalVerdict, mState)).isFalse();
useIntent(SYSTEM_UID);
@@ -140,7 +140,7 @@ public class BackgroundActivityStartControllerLogTests {
@Test
public void pendingIntent_shouldLogIntentActivityForCaller() {
- BalVerdict finalVerdict = new BalVerdict(BAL_ALLOW_SAW_PERMISSION, false, "SAW");
+ BalVerdict finalVerdict = new BalVerdict(BAL_ALLOW_SAW_PERMISSION, "SAW");
usePendingIntent(APP1_UID, APP2_UID);
assertThat(mController.shouldLogIntentActivity(finalVerdict, mState)).isFalse();
usePendingIntent(SYSTEM_UID, SYSTEM_UID);
@@ -153,7 +153,7 @@ public class BackgroundActivityStartControllerLogTests {
@Test
public void pendingIntent_shouldLogIntentActivityForRealCaller() {
- BalVerdict finalVerdict = new BalVerdict(BAL_ALLOW_SAW_PERMISSION, false,
+ BalVerdict finalVerdict = new BalVerdict(BAL_ALLOW_SAW_PERMISSION,
"SAW").setBasedOnRealCaller();
usePendingIntent(APP1_UID, APP2_UID);
assertThat(mController.shouldLogIntentActivity(finalVerdict, mState)).isFalse();
@@ -168,7 +168,7 @@ public class BackgroundActivityStartControllerLogTests {
@Test
public void pendingIntent_realCallerOnly_visible_noLog() {
usePendingIntent();
- BalVerdict finalVerdict = new BalVerdict(BAL_ALLOW_VISIBLE_WINDOW, false,
+ BalVerdict finalVerdict = new BalVerdict(BAL_ALLOW_VISIBLE_WINDOW,
"visible").setBasedOnRealCaller();
mState.setResultForCaller(BalVerdict.BLOCK);
mState.setResultForRealCaller(finalVerdict);
@@ -178,7 +178,7 @@ public class BackgroundActivityStartControllerLogTests {
@Test
public void pendingIntent_callerOnly_visible_noLog() {
usePendingIntent();
- BalVerdict finalVerdict = new BalVerdict(BAL_ALLOW_VISIBLE_WINDOW, false, "visible");
+ BalVerdict finalVerdict = new BalVerdict(BAL_ALLOW_VISIBLE_WINDOW, "visible");
mState.setResultForCaller(finalVerdict);
mState.setResultForRealCaller(BalVerdict.BLOCK);
assertThat(mController.shouldLogStats(finalVerdict, mState)).isTrue();
diff --git a/services/tests/wmtests/src/com/android/server/wm/BackgroundActivityStartControllerTests.java b/services/tests/wmtests/src/com/android/server/wm/BackgroundActivityStartControllerTests.java
index 51706d72cb35..fe9a6e746513 100644
--- a/services/tests/wmtests/src/com/android/server/wm/BackgroundActivityStartControllerTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/BackgroundActivityStartControllerTests.java
@@ -305,7 +305,7 @@ public class BackgroundActivityStartControllerTests {
@Test
public void testRegularActivityStart_allowedByCaller_isAllowed() {
// setup state
- BalVerdict callerVerdict = new BalVerdict(BAL_ALLOW_VISIBLE_WINDOW, false,
+ BalVerdict callerVerdict = new BalVerdict(BAL_ALLOW_VISIBLE_WINDOW,
"CallerIsVisible");
mController.setCallerVerdict(callerVerdict);
mController.setRealCallerVerdict(BalVerdict.BLOCK);
@@ -340,7 +340,7 @@ public class BackgroundActivityStartControllerTests {
@Test
public void testRegularActivityStart_allowedByRealCaller_isAllowed() {
// setup state
- BalVerdict realCallerVerdict = new BalVerdict(BAL_ALLOW_VISIBLE_WINDOW, false,
+ BalVerdict realCallerVerdict = new BalVerdict(BAL_ALLOW_VISIBLE_WINDOW,
"RealCallerIsVisible");
mController.setCallerVerdict(BalVerdict.BLOCK);
mController.setRealCallerVerdict(realCallerVerdict);
@@ -373,9 +373,9 @@ public class BackgroundActivityStartControllerTests {
public void testRegularActivityStart_allowedByCallerAndRealCaller_returnsCallerVerdict() {
// setup state
BalVerdict callerVerdict =
- new BalVerdict(BAL_ALLOW_PERMISSION, false, "CallerHasPermission");
+ new BalVerdict(BAL_ALLOW_PERMISSION, "CallerHasPermission");
BalVerdict realCallerVerdict =
- new BalVerdict(BAL_ALLOW_VISIBLE_WINDOW, false, "RealCallerIsVisible");
+ new BalVerdict(BAL_ALLOW_VISIBLE_WINDOW, "RealCallerIsVisible");
mController.setCallerVerdict(callerVerdict);
mController.setRealCallerVerdict(realCallerVerdict);
@@ -411,9 +411,9 @@ public class BackgroundActivityStartControllerTests {
public void testPendingIntent_allowedByCallerAndRealCallerButOptOut_isBlocked() {
// setup state
BalVerdict callerVerdict =
- new BalVerdict(BAL_ALLOW_PERMISSION, false, "CallerhasPermission");
+ new BalVerdict(BAL_ALLOW_PERMISSION, "CallerhasPermission");
BalVerdict realCallerVerdict =
- new BalVerdict(BAL_ALLOW_VISIBLE_WINDOW, false, "RealCallerIsVisible");
+ new BalVerdict(BAL_ALLOW_VISIBLE_WINDOW, "RealCallerIsVisible");
mController.setCallerVerdict(callerVerdict);
mController.setRealCallerVerdict(realCallerVerdict);
@@ -452,7 +452,7 @@ public class BackgroundActivityStartControllerTests {
public void testPendingIntent_allowedByCallerAndOptIn_isAllowed() {
// setup state
BalVerdict callerVerdict =
- new BalVerdict(BAL_ALLOW_VISIBLE_WINDOW, false, "CallerIsVisible");
+ new BalVerdict(BAL_ALLOW_VISIBLE_WINDOW, "CallerIsVisible");
mController.setCallerVerdict(callerVerdict);
mController.setRealCallerVerdict(BalVerdict.BLOCK);
@@ -489,7 +489,7 @@ public class BackgroundActivityStartControllerTests {
public void testPendingIntent_allowedByRealCallerAndOptIn_isAllowed() {
// setup state
BalVerdict realCallerVerdict =
- new BalVerdict(BAL_ALLOW_VISIBLE_WINDOW, false, "RealCallerIsVisible");
+ new BalVerdict(BAL_ALLOW_VISIBLE_WINDOW, "RealCallerIsVisible");
mController.setCallerVerdict(BalVerdict.BLOCK);
mController.setRealCallerVerdict(realCallerVerdict);
@@ -571,7 +571,6 @@ public class BackgroundActivityStartControllerTests {
+ "callerApp: mCallerApp; "
+ "inVisibleTask: false; "
+ "balAllowedByPiCreator: BSP.ALLOW_BAL; "
- + "balAllowedByPiCreatorWithHardening: BSP.ALLOW_BAL; "
+ "callerStartMode: MODE_BACKGROUND_ACTIVITY_START_SYSTEM_DEFINED; "
+ "hasRealCaller: true; "
+ "isCallForResult: false; "
@@ -674,7 +673,6 @@ public class BackgroundActivityStartControllerTests {
+ "callerApp: mCallerApp; "
+ "inVisibleTask: false; "
+ "balAllowedByPiCreator: BSP.NONE; "
- + "balAllowedByPiCreatorWithHardening: BSP.NONE; "
+ "callerStartMode: MODE_BACKGROUND_ACTIVITY_START_SYSTEM_DEFINED; "
+ "hasRealCaller: true; "
+ "isCallForResult: false; "
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 50876c7a6758..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,558 +161,680 @@ 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)
- public void testIsFreeformLetterboxingForCameraAllowed_overrideDisabled_returnsFalse() {
- configureActivity(SCREEN_ORIENTATION_PORTRAIT);
+ @DisableFlags(FLAG_ENABLE_CAMERA_COMPAT_FOR_DESKTOP_WINDOWING_OPT_OUT)
+ public void testIsFreeformLetterboxingForCameraAllowed_optInMechanism_notOptedIn_retFalse() {
+ runTestScenario((robot) -> {
+ robot.configureActivity(SCREEN_ORIENTATION_PORTRAIT);
+
+ robot.onCameraOpened(CAMERA_ID_1, TEST_PACKAGE_1);
+
+ 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() {
+ 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(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
@EnableFlags(FLAG_ENABLE_CAMERA_COMPAT_FOR_DESKTOP_WINDOWING)
+ @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() {
+ runTestScenario((robot) -> {
+ robot.configureActivity(SCREEN_ORIENTATION_PORTRAIT);
+
+ robot.onCameraOpened(CAMERA_ID_1, TEST_PACKAGE_1);
+
+ 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);
+
+ robot.onCameraOpened(CAMERA_ID_1, TEST_PACKAGE_1);
- assertTrue(mCameraCompatFreeformPolicy.shouldRefreshActivity(mActivity, newConfiguration,
- oldConfiguration));
+ 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/ClientLifecycleManagerTests.java b/services/tests/wmtests/src/com/android/server/wm/ClientLifecycleManagerTests.java
index 02ad9dbfda8e..8d214dd2c940 100644
--- a/services/tests/wmtests/src/com/android/server/wm/ClientLifecycleManagerTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/ClientLifecycleManagerTests.java
@@ -16,11 +16,13 @@
package com.android.server.wm;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.doThrow;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.spy;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify;
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.ArgumentMatchers.any;
@@ -32,17 +34,19 @@ import android.app.IApplicationThread;
import android.app.servertransaction.ActivityLifecycleItem;
import android.app.servertransaction.ClientTransaction;
import android.app.servertransaction.ClientTransactionItem;
+import android.os.DeadObjectException;
import android.os.IBinder;
import android.os.RemoteException;
+import android.platform.test.annotations.EnableFlags;
import android.platform.test.annotations.Presubmit;
import androidx.test.filters.SmallTest;
+import com.android.window.flags.Flags;
+
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;
@@ -60,15 +64,11 @@ public class ClientLifecycleManagerTests extends SystemServiceTestsBase {
@Mock
private IApplicationThread mClient;
@Mock
- private IApplicationThread.Stub mNonBinderClient;
- @Mock
private ClientTransaction mTransaction;
@Mock
private ClientTransactionItem mTransactionItem;
@Mock
private ActivityLifecycleItem mLifecycleItem;
- @Captor
- private ArgumentCaptor<ClientTransaction> mTransactionCaptor;
private WindowManagerService mWms;
private ClientLifecycleManager mLifecycleManager;
@@ -83,7 +83,6 @@ public class ClientLifecycleManagerTests extends SystemServiceTestsBase {
doReturn(true).when(mLifecycleItem).isActivityLifecycleItem();
doReturn(mClientBinder).when(mClient).asBinder();
- doReturn(mNonBinderClient).when(mNonBinderClient).asBinder();
}
@Test
@@ -91,13 +90,11 @@ public class ClientLifecycleManagerTests extends SystemServiceTestsBase {
spyOn(mWms.mWindowPlacerLocked);
doReturn(true).when(mWms.mWindowPlacerLocked).isTraversalScheduled();
- // Use non binder client to get non-recycled ClientTransaction.
- mLifecycleManager.scheduleTransactionItem(mNonBinderClient, mTransactionItem);
+ mLifecycleManager.scheduleTransactionItem(mClient, mTransactionItem);
// When there is traversal scheduled, add transaction items to pending.
assertEquals(1, mLifecycleManager.mPendingTransactions.size());
- ClientTransaction transaction =
- mLifecycleManager.mPendingTransactions.get(mNonBinderClient);
+ ClientTransaction transaction = mLifecycleManager.mPendingTransactions.get(mClientBinder);
assertEquals(1, transaction.getTransactionItems().size());
assertEquals(mTransactionItem, transaction.getTransactionItems().get(0));
// TODO(b/324203798): cleanup after remove UnsupportedAppUsage
@@ -108,10 +105,10 @@ public class ClientLifecycleManagerTests extends SystemServiceTestsBase {
// Add new transaction item to the existing pending.
clearInvocations(mLifecycleManager);
- mLifecycleManager.scheduleTransactionItem(mNonBinderClient, mLifecycleItem);
+ mLifecycleManager.scheduleTransactionItem(mClient, mLifecycleItem);
assertEquals(1, mLifecycleManager.mPendingTransactions.size());
- transaction = mLifecycleManager.mPendingTransactions.get(mNonBinderClient);
+ transaction = mLifecycleManager.mPendingTransactions.get(mClientBinder);
assertEquals(2, transaction.getTransactionItems().size());
assertEquals(mTransactionItem, transaction.getTransactionItems().get(0));
assertEquals(mLifecycleItem, transaction.getTransactionItems().get(1));
@@ -124,8 +121,7 @@ public class ClientLifecycleManagerTests extends SystemServiceTestsBase {
@Test
public void testScheduleTransactionItemNow() throws RemoteException {
- // Use non binder client to get non-recycled ClientTransaction.
- mLifecycleManager.scheduleTransactionItemNow(mNonBinderClient, mTransactionItem);
+ mLifecycleManager.scheduleTransactionItemNow(mClient, mTransactionItem);
// Dispatch immediately.
assertTrue(mLifecycleManager.mPendingTransactions.isEmpty());
@@ -137,13 +133,11 @@ public class ClientLifecycleManagerTests extends SystemServiceTestsBase {
spyOn(mWms.mWindowPlacerLocked);
doReturn(true).when(mWms.mWindowPlacerLocked).isTraversalScheduled();
- // Use non binder client to get non-recycled ClientTransaction.
- mLifecycleManager.scheduleTransactionItems(mNonBinderClient, mTransactionItem,
- mLifecycleItem);
+ mLifecycleManager.scheduleTransactionItems(mClient, mTransactionItem, mLifecycleItem);
assertEquals(1, mLifecycleManager.mPendingTransactions.size());
final ClientTransaction transaction =
- mLifecycleManager.mPendingTransactions.get(mNonBinderClient);
+ mLifecycleManager.mPendingTransactions.get(mClientBinder);
assertEquals(2, transaction.getTransactionItems().size());
assertEquals(mTransactionItem, transaction.getTransactionItems().get(0));
assertEquals(mLifecycleItem, transaction.getTransactionItems().get(1));
@@ -160,8 +154,8 @@ public class ClientLifecycleManagerTests extends SystemServiceTestsBase {
spyOn(mWms.mWindowPlacerLocked);
doReturn(true).when(mWms.mWindowPlacerLocked).isTraversalScheduled();
- // Use non binder client to get non-recycled ClientTransaction.
- mLifecycleManager.scheduleTransactionItems(mNonBinderClient,
+ mLifecycleManager.scheduleTransactionItems(
+ mClient,
true /* shouldDispatchImmediately */,
mTransactionItem, mLifecycleItem);
@@ -187,7 +181,7 @@ public class ClientLifecycleManagerTests extends SystemServiceTestsBase {
doReturn(true).when(mWms.mWindowPlacerLocked).isLayoutDeferred();
// Queue transactions during layout deferred.
- mLifecycleManager.scheduleTransactionItem(mNonBinderClient, mLifecycleItem);
+ mLifecycleManager.scheduleTransactionItem(mClient, mLifecycleItem);
verify(mLifecycleManager, never()).scheduleTransaction(any());
@@ -203,4 +197,42 @@ public class ClientLifecycleManagerTests extends SystemServiceTestsBase {
verify(mLifecycleManager).scheduleTransaction(any());
}
+
+ @EnableFlags(Flags.FLAG_CLEANUP_DISPATCH_PENDING_TRANSACTIONS_REMOTE_EXCEPTION)
+ @Test
+ public void testOnRemoteException_returnTrueOnSuccess() throws RemoteException {
+ final boolean res = mLifecycleManager.scheduleTransactionItemNow(mClient, mTransactionItem);
+
+ assertTrue(res);
+ }
+
+ @EnableFlags(Flags.FLAG_CLEANUP_DISPATCH_PENDING_TRANSACTIONS_REMOTE_EXCEPTION)
+ @Test
+ public void testOnRemoteException_returnFalseOnFailure() throws RemoteException {
+ final DeadObjectException e = new DeadObjectException();
+ doThrow(e).when(mClient).scheduleTransaction(any());
+
+ // No exception when flag enabled.
+ final boolean res = mLifecycleManager.scheduleTransactionItemNow(mClient, mTransactionItem);
+
+ assertFalse(res);
+ }
+
+ @EnableFlags(Flags.FLAG_CLEANUP_DISPATCH_PENDING_TRANSACTIONS_REMOTE_EXCEPTION)
+ @Test
+ public void testOnRemoteException_returnTrueForQueueing() throws RemoteException {
+ spyOn(mWms.mWindowPlacerLocked);
+ doReturn(true).when(mWms.mWindowPlacerLocked).isLayoutDeferred();
+ final DeadObjectException e = new DeadObjectException();
+ doThrow(e).when(mClient).scheduleTransaction(any());
+
+ final boolean res = mLifecycleManager.scheduleTransactionItem(mClient, mTransactionItem);
+
+ assertTrue(res);
+
+ doReturn(false).when(mWms.mWindowPlacerLocked).isLayoutDeferred();
+ mLifecycleManager.onLayoutContinued();
+
+ verify(mLifecycleManager).scheduleTransaction(any());
+ }
}
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 3a06fff1d1a7..e87e107cd793 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;
@@ -42,8 +43,10 @@ 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.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;
import static com.android.server.wm.LaunchParamsController.LaunchParamsModifier.PHASE_DISPLAY;
import static com.android.server.wm.LaunchParamsController.LaunchParamsModifier.RESULT_CONTINUE;
+import static com.android.server.wm.LaunchParamsController.LaunchParamsModifier.RESULT_DONE;
import static com.android.server.wm.LaunchParamsController.LaunchParamsModifier.RESULT_SKIP;
import static org.junit.Assert.assertEquals;
@@ -60,6 +63,7 @@ import android.graphics.Rect;
import android.platform.test.annotations.DisableFlags;
import android.platform.test.annotations.EnableFlags;
import android.platform.test.annotations.Presubmit;
+import android.util.Size;
import android.view.Gravity;
import androidx.test.filters.SmallTest;
@@ -154,7 +158,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();
@@ -162,7 +166,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);
@@ -266,6 +270,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() {
@@ -1099,6 +1135,110 @@ public class DesktopModeLaunchParamsModifierTests extends
}
@Test
+ @EnableFlags({Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MODE,
+ Flags.FLAG_ENABLE_SHELL_INITIAL_BOUNDS_REGRESSION_BUG_FIX})
+ public void testOptionsBoundsSet_flexibleLaunchSize_windowingModeSet() {
+ 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);
+ options.setLaunchWindowingMode(WINDOWING_MODE_FREEFORM);
+
+ assertEquals(RESULT_DONE,
+ new CalculateRequestBuilder().setTask(task).setOptions(options).calculate());
+ assertEquals(WINDOWING_MODE_FREEFORM, mResult.mWindowingMode);
+ }
+
+ @Test
+ @EnableFlags({Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MODE,
+ Flags.FLAG_ENABLE_SHELL_INITIAL_BOUNDS_REGRESSION_BUG_FIX})
+ public void testOptionsBoundsSet_flexibleLaunchSize_boundsSizeModified() {
+ 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);
+ final int modifiedWidth =
+ (int) (DISPLAY_BOUNDS.width() * DESKTOP_MODE_INITIAL_BOUNDS_SCALE);
+ final int modifiedHeight =
+ (int) (DISPLAY_BOUNDS.height() * DESKTOP_MODE_INITIAL_BOUNDS_SCALE);
+
+ assertEquals(RESULT_DONE,
+ new CalculateRequestBuilder().setTask(task).setOptions(options).calculate());
+ assertEquals(modifiedWidth, mResult.mBounds.width());
+ assertEquals(modifiedHeight, mResult.mBounds.height());
+ }
+
+ @Test
+ @EnableFlags({Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MODE,
+ Flags.FLAG_ENABLE_SHELL_INITIAL_BOUNDS_REGRESSION_BUG_FIX})
+ public void testOptionsBoundsSet_flexibleLaunchSizeWithCascading_cornerCascadeRespected() {
+ setupDesktopModeLaunchParamsModifier();
+
+ final TestDisplayContent display = createNewDisplayContent(WINDOWING_MODE_FULLSCREEN);
+ final Task task = new TaskBuilder(mSupervisor).setActivityType(
+ ACTIVITY_TYPE_STANDARD).setDisplay(display).build();
+ // Set launch bounds with corner cascade.
+ final ActivityOptions options = ActivityOptions.makeBasic()
+ .setLaunchBounds(new Rect(
+ DISPLAY_STABLE_BOUNDS.left,
+ DISPLAY_STABLE_BOUNDS.top,
+ /* right = */ 500,
+ /* bottom = */ 500))
+ .setFlexibleLaunchSize(true);
+
+ assertEquals(RESULT_DONE,
+ new CalculateRequestBuilder().setTask(task).setOptions(options).calculate());
+ assertEquals(DISPLAY_STABLE_BOUNDS.left, mResult.mBounds.left);
+ assertEquals(DISPLAY_STABLE_BOUNDS.top, mResult.mBounds.top);
+ }
+
+ @Test
+ @EnableFlags({Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MODE,
+ Flags.FLAG_ENABLE_SHELL_INITIAL_BOUNDS_REGRESSION_BUG_FIX})
+ public void testOptionsBoundsSet_flexibleLaunchSize_centerCascadeRespected() {
+ setupDesktopModeLaunchParamsModifier();
+
+ final TestDisplayContent display = createNewDisplayContent(WINDOWING_MODE_FULLSCREEN);
+ final Task task = new TaskBuilder(mSupervisor).setActivityType(
+ ACTIVITY_TYPE_STANDARD).setDisplay(display).build();
+ // Set launch bounds with center cascade.
+ final ActivityOptions options = ActivityOptions.makeBasic()
+ .setLaunchBounds(new Rect(
+ /* left = */ 320,
+ /* top = */ 100,
+ /* right = */ 640,
+ /* bottom = */ 200))
+ .setFlexibleLaunchSize(true);
+ final int modifiedWidth =
+ (int) (DISPLAY_BOUNDS.width() * DESKTOP_MODE_INITIAL_BOUNDS_SCALE);
+ final int modifiedHeight =
+ (int) (DISPLAY_BOUNDS.height() * DESKTOP_MODE_INITIAL_BOUNDS_SCALE);
+ final Rect centerCascadedBounds = centerInScreen(
+ new Size(modifiedWidth, modifiedHeight), DISPLAY_STABLE_BOUNDS);
+
+ assertEquals(RESULT_DONE,
+ new CalculateRequestBuilder().setTask(task).setOptions(options).calculate());
+ assertEquals(centerCascadedBounds, mResult.mBounds);
+ assertEquals(centerCascadedBounds.top, mResult.mBounds.top);
+ }
+
+ @Test
@EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MODE)
public void testNonEmptyLayoutBounds_CenterToDisplay() {
setupDesktopModeLaunchParamsModifier();
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/DisplayContentTests.java b/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java
index 61ed0b53cdcf..4c81f738138a 100644
--- a/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java
@@ -87,7 +87,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;
@@ -2631,10 +2631,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 +2663,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 +2686,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 +2716,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 +2925,7 @@ public class DisplayContentTests extends WindowTestsBase {
assertFalse(dc.mWmService.mDisplayWindowSettings.shouldShowSystemDecorsLocked(dc));
}
- @EnableFlags(FLAG_ENABLE_PERSISTING_DENSITY_SCALE_FOR_CONNECTED_DISPLAYS)
+ @EnableFlags(FLAG_ENABLE_PERSISTING_DISPLAY_SIZE_FOR_CONNECTED_DISPLAYS)
@Test
public void testForcedDensityRatioSetForExternalDisplays_persistDensityScaleFlagEnabled() {
final DisplayInfo displayInfo = new DisplayInfo(mDisplayInfo);
@@ -2951,7 +2955,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);
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..81e487a67725 100644
--- a/services/tests/wmtests/src/com/android/server/wm/DisplayWindowSettingsTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/DisplayWindowSettingsTests.java
@@ -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/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/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/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..e2c4a1d2dfea 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);
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..b2c195e8ebaa 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/WindowOrganizerTests.java b/services/tests/wmtests/src/com/android/server/wm/WindowOrganizerTests.java
index 7030d986494f..ae6144713a1d 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WindowOrganizerTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowOrganizerTests.java
@@ -929,7 +929,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);
diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowProcessControllerTests.java b/services/tests/wmtests/src/com/android/server/wm/WindowProcessControllerTests.java
index d228970e0371..9dc70266bf3d 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WindowProcessControllerTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowProcessControllerTests.java
@@ -24,7 +24,6 @@ import static android.content.res.Configuration.GRAMMATICAL_GENDER_NOT_SPECIFIED
import static android.content.res.Configuration.ORIENTATION_LANDSCAPE;
import static android.content.res.Configuration.ORIENTATION_PORTRAIT;
-import static com.android.dx.mockito.inline.extended.ExtendedMockito.doNothing;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.never;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify;
@@ -295,7 +294,7 @@ public class WindowProcessControllerTests extends WindowTestsBase {
@Test
public void testCachedStateConfigurationChange() throws RemoteException {
- doNothing().when(mClientLifecycleManager).scheduleTransactionItemNow(any(), any());
+ doReturn(true).when(mClientLifecycleManager).scheduleTransactionItemNow(any(), any());
final IApplicationThread thread = mWpc.getThread();
final Configuration newConfig = new Configuration(mWpc.getConfiguration());
newConfig.densityDpi += 100;
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/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/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/TelephonyManager.java b/telephony/java/android/telephony/TelephonyManager.java
index fbba999bfb36..14d567d141cb 100644
--- a/telephony/java/android/telephony/TelephonyManager.java
+++ b/telephony/java/android/telephony/TelephonyManager.java
@@ -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/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/AppJankTest/src/android/app/jank/tests/JankTrackerTest.java b/tests/AppJankTest/src/android/app/jank/tests/JankTrackerTest.java
index 1bdf019d6c42..9d87fbd67eef 100644
--- a/tests/AppJankTest/src/android/app/jank/tests/JankTrackerTest.java
+++ b/tests/AppJankTest/src/android/app/jank/tests/JankTrackerTest.java
@@ -18,10 +18,12 @@ package android.app.jank.tests;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
import android.app.jank.Flags;
import android.app.jank.JankTracker;
import android.app.jank.StateTracker;
+import android.content.Context;
import android.platform.test.annotations.RequiresFlagsEnabled;
import android.platform.test.flag.junit.CheckFlagsRule;
import android.platform.test.flag.junit.DeviceFlagsValueProvider;
@@ -55,10 +57,9 @@ public class JankTrackerTest {
* Start an empty activity so decore view is not null when creating the JankTracker instance.
*/
private static ActivityScenario<EmptyActivity> sEmptyActivityRule;
-
private static String sActivityName;
-
private static View sActivityDecorView;
+ private static Context sContext;
@BeforeClass
public static void classSetup() {
@@ -66,6 +67,7 @@ public class JankTrackerTest {
sEmptyActivityRule.onActivity(activity -> {
sActivityDecorView = activity.getWindow().getDecorView();
sActivityName = activity.toString();
+ sContext = activity.getApplicationContext();
});
}
@@ -168,4 +170,14 @@ public class JankTrackerTest {
assertNotNull(jankTracker);
}
+
+ @Test
+ @RequiresFlagsEnabled(Flags.FLAG_DETAILED_APP_JANK_METRICS_API)
+ public void jankTracker_IsNull_WhenViewNotInHierarchy() {
+ TestWidget testWidget = new TestWidget(sContext);
+ JankTracker jankTracker = testWidget.getJankTracker();
+
+ assertNull(jankTracker);
+ }
+
}
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/Android.bp b/tests/Input/Android.bp
index 1f0bd61b5c3f..544f94b49ee7 100644
--- a/tests/Input/Android.bp
+++ b/tests/Input/Android.bp
@@ -15,6 +15,7 @@ android_test {
"modules-utils-testable-device-config-defaults",
],
srcs: [
+ "src/**/*.aidl",
"src/**/*.java",
"src/**/*.kt",
],
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/InputManagerServiceTests.kt b/tests/Input/src/com/android/server/input/InputManagerServiceTests.kt
index eac426700ec1..7ec8f9ce9864 100644
--- a/tests/Input/src/com/android/server/input/InputManagerServiceTests.kt
+++ b/tests/Input/src/com/android/server/input/InputManagerServiceTests.kt
@@ -76,7 +76,7 @@ import org.mockito.Mockito.spy
import org.mockito.Mockito.times
import org.mockito.Mockito.verify
import org.mockito.Mockito.verifyNoMoreInteractions
-import org.mockito.Mockito.verifyZeroInteractions
+import org.mockito.Mockito.verifyNoInteractions
import org.mockito.Mockito.`when`
import org.mockito.stubbing.OngoingStubbing
@@ -209,7 +209,7 @@ class InputManagerServiceTests {
@Test
fun testStart() {
- verifyZeroInteractions(native)
+ verifyNoInteractions(native)
service.start()
verify(native).start()
@@ -217,7 +217,7 @@ class InputManagerServiceTests {
@Test
fun testInputSettingsUpdatedOnSystemRunning() {
- verifyZeroInteractions(native)
+ verifyNoInteractions(native)
runWithShellPermissionIdentity {
service.systemRunning()
diff --git a/tests/Input/src/com/android/server/input/KeyGestureControllerTests.kt b/tests/Input/src/com/android/server/input/KeyGestureControllerTests.kt
index c666fb7e05f1..2799f6c79215 100644
--- a/tests/Input/src/com/android/server/input/KeyGestureControllerTests.kt
+++ b/tests/Input/src/com/android/server/input/KeyGestureControllerTests.kt
@@ -382,14 +382,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,
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/Input/src/com/android/test/input/AnrTest.kt b/tests/Input/src/com/android/test/input/AnrTest.kt
index 73192eac89c0..f8cb86b7b1fe 100644
--- a/tests/Input/src/com/android/test/input/AnrTest.kt
+++ b/tests/Input/src/com/android/test/input/AnrTest.kt
@@ -15,32 +15,37 @@
*/
package com.android.test.input
-import androidx.test.ext.junit.runners.AndroidJUnit4
-import androidx.test.platform.app.InstrumentationRegistry
-import androidx.test.filters.MediumTest
-
import android.app.ActivityManager
import android.app.ApplicationExitInfo
-import android.content.Context
-import android.graphics.Rect
+import android.app.Instrumentation
+import android.content.Intent
import android.hardware.display.DisplayManager
import android.os.Build
+import android.os.Bundle
+import android.os.IBinder
import android.os.IInputConstants.UNMULTIPLIED_DEFAULT_DISPATCHING_TIMEOUT_MILLIS
import android.os.SystemClock
-import android.server.wm.CtsWindowInfoUtils.waitForStableWindowGeometry
+import android.server.wm.CtsWindowInfoUtils.getWindowCenter
+import android.server.wm.CtsWindowInfoUtils.waitForWindowOnTop
import android.testing.PollingCheck
-
+import android.view.InputEvent
+import android.view.MotionEvent
+import android.view.MotionEvent.ACTION_DOWN
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.MediumTest
+import androidx.test.platform.app.InstrumentationRegistry
import androidx.test.uiautomator.By
import androidx.test.uiautomator.UiDevice
import androidx.test.uiautomator.UiObject2
import androidx.test.uiautomator.Until
-
+import com.android.cts.input.BlockingQueueEventVerifier
import com.android.cts.input.DebugInputRule
import com.android.cts.input.ShowErrorDialogsRule
import com.android.cts.input.UinputTouchScreen
-
+import com.android.cts.input.inputeventmatchers.withMotionAction
import java.time.Duration
-
+import java.util.concurrent.LinkedBlockingQueue
+import java.util.function.Supplier
import org.junit.After
import org.junit.Assert.assertEquals
import org.junit.Assert.assertTrue
@@ -51,13 +56,34 @@ import org.junit.Test
import org.junit.runner.RunWith
/**
+ * Click on the center of the window identified by the provided window token.
+ * The click is performed using "UinputTouchScreen" device.
+ * If the touchscreen device is closed too soon, it may cause the click to be dropped. Therefore,
+ * the provided runnable can ensure that the click is delivered before the device is closed, thus
+ * avoiding this race.
+ */
+private fun clickOnWindow(
+ token: IBinder,
+ displayId: Int,
+ instrumentation: Instrumentation,
+ waitForEvent: Runnable,
+) {
+ val displayManager = instrumentation.context.getSystemService(DisplayManager::class.java)
+ val display = displayManager.getDisplay(displayId)
+ val point = getWindowCenter({ token }, display.displayId)
+ UinputTouchScreen(instrumentation, display).use { touchScreen ->
+ touchScreen.touchDown(point.x, point.y).lift()
+ // If the device is allowed to close without waiting here, the injected click may be dropped
+ waitForEvent.run()
+ }
+}
+
+/**
* This test makes sure that an unresponsive gesture monitor gets an ANR.
*
* The gesture monitor must be registered from a different process than the instrumented process.
- * Otherwise, when the test runs, you will get:
- * Test failed to run to completion.
- * Reason: 'Instrumentation run failed due to 'keyDispatchingTimedOut''.
- * Check device logcat for details
+ * Otherwise, when the test runs, you will get: Test failed to run to completion. Reason:
+ * 'Instrumentation run failed due to 'keyDispatchingTimedOut''. Check device logcat for details
* RUNNER ERROR: Instrumentation run failed due to 'keyDispatchingTimedOut'
*/
@MediumTest
@@ -65,30 +91,43 @@ import org.junit.runner.RunWith
class AnrTest {
companion object {
private const val TAG = "AnrTest"
- private const val ALL_PIDS = 0
private const val NO_MAX = 0
}
private val instrumentation = InstrumentationRegistry.getInstrumentation()
- private var hideErrorDialogs = 0
private lateinit var PACKAGE_NAME: String
- private val DISPATCHING_TIMEOUT = (UNMULTIPLIED_DEFAULT_DISPATCHING_TIMEOUT_MILLIS *
- Build.HW_TIMEOUT_MULTIPLIER)
+ private val DISPATCHING_TIMEOUT =
+ (UNMULTIPLIED_DEFAULT_DISPATCHING_TIMEOUT_MILLIS * Build.HW_TIMEOUT_MULTIPLIER)
+ private var remoteWindowToken: IBinder? = null
+ private var remoteDisplayId: Int? = null
+ private var remotePid: Int? = null
+ private val remoteInputEvents = LinkedBlockingQueue<InputEvent>()
+ private val verifier = BlockingQueueEventVerifier(remoteInputEvents)
+
+ val binder =
+ object : IAnrTestService.Stub() {
+ override fun provideActivityInfo(token: IBinder, displayId: Int, pid: Int) {
+ remoteWindowToken = token
+ remoteDisplayId = displayId
+ remotePid = pid
+ }
+
+ override fun notifyMotion(event: MotionEvent) {
+ remoteInputEvents.add(event)
+ }
+ }
- @get:Rule
- val debugInputRule = DebugInputRule()
+ @get:Rule val showErrorDialogs = ShowErrorDialogsRule()
- @get:Rule
- val showErrorDialogs = ShowErrorDialogsRule()
+ @get:Rule val debugInputRule = DebugInputRule()
@Before
fun setUp() {
+ startUnresponsiveActivity()
PACKAGE_NAME = UnresponsiveGestureMonitorActivity::class.java.getPackage()!!.getName()
}
- @After
- fun tearDown() {
- }
+ @After fun tearDown() {}
@Test
@DebugInputRule.DebugInput(bug = 339924248)
@@ -112,7 +151,7 @@ class AnrTest {
val timestamp = System.currentTimeMillis()
val uiDevice: UiDevice = UiDevice.getInstance(instrumentation)
val closeAppButton: UiObject2? =
- uiDevice.wait(Until.findObject(By.res("android:id/aerr_close")), 20000)
+ uiDevice.wait(Until.findObject(By.res("android:id/aerr_close")), 20000)
if (closeAppButton == null) {
fail("Could not find anr dialog/close button")
return
@@ -120,10 +159,10 @@ class AnrTest {
closeAppButton.click()
/**
* We must wait for the app to be fully closed before exiting this test. This is because
- * another test may again invoke 'am start' for the same activity.
- * If the 1st process that got ANRd isn't killed by the time second 'am start' runs,
- * the killing logic will apply to the newly launched 'am start' instance, and the second
- * test will fail because the unresponsive activity will never be launched.
+ * another test may again invoke 'am start' for the same activity. If the 1st process that
+ * got ANRd isn't killed by the time second 'am start' runs, the killing logic will apply to
+ * the newly launched 'am start' instance, and the second test will fail because the
+ * unresponsive activity will never be launched.
*/
waitForNewExitReasonAfter(timestamp)
}
@@ -132,7 +171,7 @@ class AnrTest {
// Find anr dialog and tap on wait
val uiDevice: UiDevice = UiDevice.getInstance(instrumentation)
val waitButton: UiObject2? =
- uiDevice.wait(Until.findObject(By.res("android:id/aerr_wait")), 20000)
+ uiDevice.wait(Until.findObject(By.res("android:id/aerr_wait")), 20000)
if (waitButton == null) {
fail("Could not find anr dialog/wait button")
return
@@ -144,7 +183,7 @@ class AnrTest {
lateinit var infos: List<ApplicationExitInfo>
instrumentation.runOnMainSync {
val am = instrumentation.getContext().getSystemService(ActivityManager::class.java)!!
- infos = am.getHistoricalProcessExitReasons(PACKAGE_NAME, ALL_PIDS, NO_MAX)
+ infos = am.getHistoricalProcessExitReasons(PACKAGE_NAME, remotePid!!, NO_MAX)
}
return infos
}
@@ -159,37 +198,32 @@ class AnrTest {
assertEquals(ApplicationExitInfo.REASON_ANR, reasons[0].reason)
}
- private fun clickOnObject(obj: UiObject2) {
- val displayManager =
- instrumentation.context.getSystemService(Context.DISPLAY_SERVICE) as DisplayManager
- val display = displayManager.getDisplay(obj.getDisplayId())
- val rect: Rect = obj.visibleBounds
- UinputTouchScreen(instrumentation, display).use { touchScreen ->
- touchScreen
- .touchDown(rect.centerX(), rect.centerY())
- .lift()
- }
- }
-
private fun triggerAnr() {
- startUnresponsiveActivity()
- val uiDevice: UiDevice = UiDevice.getInstance(instrumentation)
- val obj: UiObject2? = uiDevice.wait(Until.findObject(By.pkg(PACKAGE_NAME)), 10000)
-
- if (obj == null) {
- fail("Could not find unresponsive activity")
- return
- }
-
- clickOnObject(obj)
+ clickOnWindow(
+ remoteWindowToken!!,
+ remoteDisplayId!!,
+ instrumentation,
+ ) { verifier.assertReceivedMotion(withMotionAction(ACTION_DOWN)) }
SystemClock.sleep(DISPATCHING_TIMEOUT.toLong()) // default ANR timeout for gesture monitors
}
private fun startUnresponsiveActivity() {
- val flags = " -W -n "
- val startCmd = "am start $flags $PACKAGE_NAME/.UnresponsiveGestureMonitorActivity"
- instrumentation.uiAutomation.executeShellCommand(startCmd)
- waitForStableWindowGeometry(Duration.ofSeconds(5))
+ val intent =
+ Intent(instrumentation.targetContext, UnresponsiveGestureMonitorActivity::class.java)
+ intent.flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_NEW_DOCUMENT
+ val bundle = Bundle()
+ bundle.putBinder("serviceBinder", binder)
+ intent.putExtra("serviceBundle", bundle)
+ instrumentation.targetContext.startActivity(intent)
+ // first, wait for the token to become valid
+ PollingCheck.check(
+ "UnresponsiveGestureMonitorActivity failed to call 'provideActivityInfo'",
+ Duration.ofSeconds(5).toMillis()) { remoteWindowToken != null }
+ // next, wait for the window of the activity to get on top
+ // we could combine the two checks above, but the current setup makes it easier to detect
+ // errors
+ assertTrue("Remote activity window did not become visible",
+ waitForWindowOnTop(Duration.ofSeconds(5), Supplier { remoteWindowToken }))
}
}
diff --git a/tests/Input/src/com/android/test/input/IAnrTestService.aidl b/tests/Input/src/com/android/test/input/IAnrTestService.aidl
new file mode 100644
index 000000000000..e3caf06b742b
--- /dev/null
+++ b/tests/Input/src/com/android/test/input/IAnrTestService.aidl
@@ -0,0 +1,17 @@
+package com.android.test.input;
+
+import android.view.MotionEvent;
+
+interface IAnrTestService {
+ /**
+ * Provide the activity information. This includes:
+ * windowToken: the windowToken of the activity window
+ * displayId: the display id on which the activity is positioned
+ * pid: the pid of the activity
+ */
+ void provideActivityInfo(IBinder windowToken, int displayId, int pid);
+ /**
+ * Provide the MotionEvent received by the remote activity.
+ */
+ void notifyMotion(in MotionEvent event);
+}
diff --git a/tests/Input/src/com/android/test/input/UnresponsiveGestureMonitorActivity.kt b/tests/Input/src/com/android/test/input/UnresponsiveGestureMonitorActivity.kt
index 1842f0a64a83..1e44617af111 100644
--- a/tests/Input/src/com/android/test/input/UnresponsiveGestureMonitorActivity.kt
+++ b/tests/Input/src/com/android/test/input/UnresponsiveGestureMonitorActivity.kt
@@ -23,20 +23,24 @@ import android.app.Activity
import android.hardware.input.InputManager
import android.os.Bundle
import android.os.Looper
+import android.os.Process
import android.util.Log
import android.view.InputChannel
import android.view.InputEvent
import android.view.InputEventReceiver
import android.view.InputMonitor
+import android.view.MotionEvent
-class UnresponsiveReceiver(channel: InputChannel, looper: Looper) :
- InputEventReceiver(channel, looper) {
+class UnresponsiveReceiver(channel: InputChannel, looper: Looper, val service: IAnrTestService) :
+ InputEventReceiver(channel, looper) {
companion object {
const val TAG = "UnresponsiveReceiver"
}
+
override fun onInputEvent(event: InputEvent) {
Log.i(TAG, "Received $event")
// Not calling 'finishInputEvent' in order to trigger the ANR
+ service.notifyMotion(event as MotionEvent)
}
}
@@ -44,14 +48,27 @@ class UnresponsiveGestureMonitorActivity : Activity() {
companion object {
const val MONITOR_NAME = "unresponsive gesture monitor"
}
+
private lateinit var mInputEventReceiver: InputEventReceiver
private lateinit var mInputMonitor: InputMonitor
+ private lateinit var service: IAnrTestService
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
+ val bundle = intent.getBundleExtra("serviceBundle")!!
+ service = IAnrTestService.Stub.asInterface(bundle.getBinder("serviceBinder"))
val inputManager = checkNotNull(getSystemService(InputManager::class.java))
mInputMonitor = inputManager.monitorGestureInput(MONITOR_NAME, displayId)
- mInputEventReceiver = UnresponsiveReceiver(
- mInputMonitor.getInputChannel(), Looper.myLooper()!!)
+ mInputEventReceiver =
+ UnresponsiveReceiver(mInputMonitor.getInputChannel(), Looper.myLooper()!!, service)
+ }
+
+ override fun onAttachedToWindow() {
+ super.onAttachedToWindow()
+ service.provideActivityInfo(
+ window.decorView.windowToken,
+ display.displayId,
+ Process.myPid(),
+ )
}
}
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/UsbTests/src/com/android/server/usb/UsbServiceTest.java b/tests/UsbTests/src/com/android/server/usb/UsbServiceTest.java
index 51d57f0a0de9..f5d4b0c5e345 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.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,
@@ -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/vcn/java/com/android/server/VcnManagementServiceTest.java b/tests/vcn/java/com/android/server/VcnManagementServiceTest.java
index a97f9a837bab..3cccbc419425 100644
--- a/tests/vcn/java/com/android/server/VcnManagementServiceTest.java
+++ b/tests/vcn/java/com/android/server/VcnManagementServiceTest.java
@@ -41,6 +41,7 @@ import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
+import static org.junit.Assume.assumeTrue;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyBoolean;
import static org.mockito.ArgumentMatchers.anyInt;
@@ -1088,6 +1089,10 @@ public class VcnManagementServiceTest {
@Test
public void testGetRestrictedTransportsFromCarrierConfig() {
+ assumeTrue(
+ "Configuring restricted transport types is only allowed on a debuggable build",
+ Build.isDebuggable());
+
final Set<Integer> restrictedTransports = new ArraySet<>();
restrictedTransports.add(TRANSPORT_CELLULAR);
restrictedTransports.add(TRANSPORT_WIFI);
@@ -1109,6 +1114,10 @@ public class VcnManagementServiceTest {
@Test
public void testGetRestrictedTransportsFromCarrierConfig_noRestrictPolicyConfigured() {
+ assumeTrue(
+ "Configuring restricted transport types is only allowed on a debuggable build",
+ Build.isDebuggable());
+
final Set<Integer> restrictedTransports = Collections.singleton(TRANSPORT_WIFI);
final PersistableBundleWrapper carrierConfig =
@@ -1123,6 +1132,10 @@ public class VcnManagementServiceTest {
@Test
public void testGetRestrictedTransportsFromCarrierConfig_noCarrierConfig() {
+ assumeTrue(
+ "Configuring restricted transport types is only allowed on a debuggable build",
+ Build.isDebuggable());
+
final Set<Integer> restrictedTransports = Collections.singleton(TRANSPORT_WIFI);
final TelephonySubscriptionSnapshot lastSnapshot =
@@ -1134,6 +1147,10 @@ public class VcnManagementServiceTest {
@Test
public void testGetRestrictedTransportsFromCarrierConfigAndVcnConfig() {
+ assumeTrue(
+ "Configuring restricted transport types is only allowed on a debuggable build",
+ Build.isDebuggable());
+
// Configure restricted transport in CarrierConfig
final Set<Integer> restrictedTransportInCarrierConfig =
Collections.singleton(TRANSPORT_WIFI);
diff --git a/tools/codegen/OWNERS b/tools/codegen/OWNERS
index c9bd260ca7ae..e69de29bb2d1 100644
--- a/tools/codegen/OWNERS
+++ b/tools/codegen/OWNERS
@@ -1 +0,0 @@
-chiuwinson@google.com
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/OWNERS b/tools/lint/OWNERS
index 8e4569ee2a30..4035e19158c6 100644
--- a/tools/lint/OWNERS
+++ b/tools/lint/OWNERS
@@ -1,6 +1,5 @@
mattgilbride@google.com
azharaa@google.com
-jsharkey@google.com
per-file *CallingSettingsNonUserGetterMethods* = file:/packages/SettingsProvider/OWNERS
per-file *RegisterReceiverFlagDetector* = jacobhobbie@google.com
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/java/src/android/net/wifi/sharedconnectivity/OWNERS b/wifi/java/src/android/net/wifi/sharedconnectivity/OWNERS
index 2a4acc111257..abb9aa4c05a2 100644
--- a/wifi/java/src/android/net/wifi/sharedconnectivity/OWNERS
+++ b/wifi/java/src/android/net/wifi/sharedconnectivity/OWNERS
@@ -1,4 +1,3 @@
# Bug component: 1216021
asapperstein@google.com
-etancohen@google.com